<?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: Robert Slootjes</title>
    <description>The latest articles on DEV Community by Robert Slootjes (@slootjes).</description>
    <link>https://dev.to/slootjes</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%2F707108%2F1310ca99-1b97-4aed-9009-e945612150e7.jpg</url>
      <title>DEV Community: Robert Slootjes</title>
      <link>https://dev.to/slootjes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/slootjes"/>
    <language>en</language>
    <item>
      <title>SQS Fair Queues with AWS EventBridge</title>
      <dc:creator>Robert Slootjes</dc:creator>
      <pubDate>Wed, 03 Dec 2025 15:45:54 +0000</pubDate>
      <link>https://dev.to/slootjes/sqs-fair-queues-with-aws-eventbridge-58i2</link>
      <guid>https://dev.to/slootjes/sqs-fair-queues-with-aws-eventbridge-58i2</guid>
      <description>&lt;p&gt;A while ago AWS introduced &lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-fair-queues.html" rel="noopener noreferrer"&gt;Fair Queues&lt;/a&gt;, a new feature for SQS. I was immediately excited and started testing this using the &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/" rel="noopener noreferrer"&gt;SDK&lt;/a&gt; it worked really well:&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;SQSClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SendMessageCommand&lt;/span&gt;&lt;span class="p"&gt;,&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;@aws-sdk/client-sqs&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;sqsClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SQSClient&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;sqsClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SendMessageCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;QueueUrl&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://sqs.eu-west-1.amazonaws.com/123/fair-queue-demo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;MessageBody&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="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bar&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;MessageGroupId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mybrand&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The message &lt;strong&gt;MessageGroupId&lt;/strong&gt; attribute has a value of &lt;em&gt;mybrand&lt;/em&gt; as expected:&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="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;foo&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;bar&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;"attributes"&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;"ApproximateReceiveCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"SentTimestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1764775946158"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MessageGroupId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mybrand"&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;h2&gt;
  
  
  EventBridge
&lt;/h2&gt;

&lt;p&gt;After the announcement of Fair Queues I tried to make it work with &lt;a href="https://aws.amazon.com/eventbridge/" rel="noopener noreferrer"&gt;EventBridge&lt;/a&gt; since we use this almost everywhere. Passing the MessageGroupId dynamically was not possible unfortunately yet when Fair Queues was first released. However recently it was &lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/11/amazon-eventbridge-sqs-fair-queue-targets/" rel="noopener noreferrer"&gt;announced&lt;/a&gt; that the MessageGroupId can now be passed in EventBridge SQS Targets so I decided to give it another try:&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;EventBridgeClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PutEventsCommand&lt;/span&gt;&lt;span class="p"&gt;,&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;@aws-sdk/client-eventbridge&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;eventBridgeClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventBridgeClient&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;eventBridgeClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PutEventsCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;Entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myapp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;DetailType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fair-queue-demo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Detail&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="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mybrand&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In combination with this 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;FairQueueDemoRule&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="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="s"&gt;myapp&lt;/span&gt;
      &lt;span class="na"&gt;detail-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fair-queue-demo&lt;/span&gt;
    &lt;span class="na"&gt;State&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ENABLED&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;Id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;FairQueueDemo&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;FairQueueDemo.Arn&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;$.detail.brand&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now it will actually set the MessageGroupId based on the contents of the detail object sent to EventBridge:&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="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;90a484f2-d2e2-2ff2-f8b2-3b64a4538721&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;fair-queue-demo&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;myapp&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;2025-12-03T15:21:16Z&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;eu-west-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;brand&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;mybrand&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;foo&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;bar&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;"attributes"&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;"ApproximateReceiveCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"SentTimestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1764775276763"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MessageGroupId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mybrand"&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;As you can see, the &lt;strong&gt;MessageGroupId&lt;/strong&gt; is set to &lt;em&gt;mybrand&lt;/em&gt; as expected 🎉&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>AWS Lamba &amp; RDS Proxy</title>
      <dc:creator>Robert Slootjes</dc:creator>
      <pubDate>Sun, 15 Jun 2025 13:03:42 +0000</pubDate>
      <link>https://dev.to/slootjes/aws-lamba-rds-proxy-4gee</link>
      <guid>https://dev.to/slootjes/aws-lamba-rds-proxy-4gee</guid>
      <description>&lt;p&gt;Probably like many others, I tried to avoid relational databases like Postgres, MySQL and MariaDB with Lambda as I've read many times that connection based services aren't a good fit for the &lt;em&gt;potentially&lt;/em&gt; rapid scaling of Lambda. So ever since I am doing serverless, I try to use DynamoDB or ElastiCache Redis/Valkey as much as possible.&lt;/p&gt;

&lt;p&gt;At some point I really needed to use a relational database and I started playing with &lt;a href="https://aws.amazon.com/rds/aurora/" rel="noopener noreferrer"&gt;RDS Aurora&lt;/a&gt;. I created an instance, connected from Lambda and it worked just fine. However when I generated a bit more load it soon started locking up, all connections were in use and new ones couldn't be created. It would take a while for the database to become available again. The warning for combining Lambda with connection based services are not there without reasons.&lt;/p&gt;

&lt;h2&gt;
  
  
  RDS Proxy
&lt;/h2&gt;

&lt;p&gt;Then, I remembered &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/rds-proxy.html" rel="noopener noreferrer"&gt;RDS Proxy&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html" rel="noopener noreferrer"&gt;RDS Data API&lt;/a&gt; to exist and now I understood why. While Data API is a great service, it does come with some &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.limitations.html" rel="noopener noreferrer"&gt;limitations&lt;/a&gt; and I decided to go with RDS Proxy. I also liked the aspect of being able to use generic libraries for connecting and querying data.&lt;/p&gt;

&lt;h2&gt;
  
  
  CloudFormation
&lt;/h2&gt;

&lt;p&gt;Getting everything to work with &lt;a href="https://aws.amazon.com/cloudformation/" rel="noopener noreferrer"&gt;CloudFormation&lt;/a&gt; and using best practices took me a while as a different setting could completely lock up the creation of a database. I also ran into issues where the RDS Proxy was created and had no errors in the console but it turned out that, using CLI tools, the network config was broken which was the reason I couldn't connect. To save you from this pain, I created a demo that sets up Lambda with RDS Aurora &amp;amp; Proxy making use of security best practices like a managed password and IAM authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/slootjes/aws-lambda-rds-proxy" rel="noopener noreferrer"&gt;demo&lt;/a&gt; is using the &lt;a href="https://github.com/oss-serverless/serverless" rel="noopener noreferrer"&gt;open source fork of Serverless Framework&lt;/a&gt; (a drop-in replacement for Serverless Framework v3!) with Typescript that will give you the following after deploying:&lt;/p&gt;

&lt;h3&gt;
  
  
  Networking
&lt;/h3&gt;

&lt;p&gt;A basic setup with a VPC, 2 private and 2 public subnets in different availability zones for high availability (thanks to my colleague Martijn).&lt;/p&gt;

&lt;h3&gt;
  
  
  RDS Aurora Postgres
&lt;/h3&gt;

&lt;p&gt;An RDS Aurora Postgres cluster with Postgres 16.6 with 2 serverless instances for Multi-AZ support. Also enabled are encrypted storage, backups, IAM authentication and serverless scaling configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database:
  Type: AWS::RDS::DBCluster
  Properties:
    ManageMasterUserPassword: True
    Engine: aurora-postgresql
    EngineVersion: 16.6
    StorageEncrypted: True
    EnableIAMDatabaseAuthentication: True
    BackupRetentionPeriod: 7
    ServerlessV2ScalingConfiguration:
      MinCapacity: 0
      MaxCapacity: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  RDS Proxy
&lt;/h3&gt;

&lt;p&gt;An RDS Proxy instance with read/write and read only endpoints making use of IAM authentication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DatabaseProxy:
  Type: AWS::RDS::DBProxy
  Properties:
    Auth:
      - AuthScheme: SECRETS
    ClientPasswordAuthType: POSTGRES_SCRAM_SHA_256
    IAMAuth: REQUIRED
    SecretArn:
      Fn::GetAtt: [ Database, MasterUserSecret.SecretArn ]
    EngineFamily: POSTGRESQL
    RequireTLS: True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lambda
&lt;/h3&gt;

&lt;p&gt;Finally a Lambda function, security group and role which can connect to the RDS Proxy using IAM authentication. Now we can just use &lt;a href="https://github.com/brianc/node-postgres" rel="noopener noreferrer"&gt;pg&lt;/a&gt; with the &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-rds-signer/" rel="noopener noreferrer"&gt;rds-signer&lt;/a&gt; package to work with Postgres the way you are used to!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const db = await getClient();
const result = await db.query('SELECT version()');

console.log(result.rows[0].version);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const db = await getClient();
const result = await db.query(
  'SELECT id, title, body FROM articles WHERE type = $1',
  ['database'],
);

for (const article of result.rows) {
  console.log(article);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Certificate
&lt;/h4&gt;

&lt;p&gt;Even though AWS points you towards a certificate for RDS per region like &lt;em&gt;&lt;a href="https://truststore.pki.rds.amazonaws.com/eu-west-1/eu-west-1-bundle.pem" rel="noopener noreferrer"&gt;https://truststore.pki.rds.amazonaws.com/eu-west-1/eu-west-1-bundle.pem&lt;/a&gt;&lt;/em&gt; this does not apply to RDS Proxy for which you will need to use &lt;em&gt;&lt;a href="https://www.amazontrust.com/repository/AmazonRootCA1.pem" rel="noopener noreferrer"&gt;https://www.amazontrust.com/repository/AmazonRootCA1.pem&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Warnings
&lt;/h4&gt;

&lt;p&gt;Obviously this is just a stripped down demo with the basics and you need to modify the template to your own needs. Even though this is mostly a serverless setup, you will be charged for RDS Aurora and RDS Proxy per hour. Because RDS Proxy will stay connected, RDS Aurora will not be able to pause / scale back down to 0:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If your Aurora cluster has an associated proxy through Amazon RDS Proxy, the proxy maintains an open connection to each DB instance in the cluster and the instance doesn’t automatically pause.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Warning&lt;/em&gt;: By deploying this demo, you acknowledge that AWS services may incur costs. I, the author, am not responsible for any charges or usage fees associated with your AWS account. Deploy at your own discretion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Management in a VPC
&lt;/h2&gt;

&lt;p&gt;It seems many people are still struggling using database tools because their RDS cluster is running inside a VPC. I recommend you to read one of my previous articles to see the &lt;a href="https://dev.to/slootjes/aws-rds-elasticache-remote-access-with-port7777-2hpn"&gt;killer tool that solved this problem for me: Port7777&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;After migrating to RDS Proxy, I did not run into performance or scalability issues anymore.&lt;/p&gt;

&lt;p&gt;To close of, again the link to the demo: &lt;a href="https://github.com/slootjes/aws-lambda-rds-proxy" rel="noopener noreferrer"&gt;https://github.com/slootjes/aws-lambda-rds-proxy&lt;/a&gt;. Let me know if you think I've missed something crucial. In the next article I will explain how to use S3 features with RDS Aurora and update the demo. Have fun!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>AWS AppSync Events vs IoT Core</title>
      <dc:creator>Robert Slootjes</dc:creator>
      <pubDate>Tue, 19 Nov 2024 18:51:34 +0000</pubDate>
      <link>https://dev.to/slootjes/aws-appsync-events-vs-iot-core-21dk</link>
      <guid>https://dev.to/slootjes/aws-appsync-events-vs-iot-core-21dk</guid>
      <description>&lt;p&gt;AWS recently announced &lt;a href="https://aws.amazon.com/blogs/mobile/announcing-aws-appsync-events-serverless-websocket-apis/" rel="noopener noreferrer"&gt;AppSync Events&lt;/a&gt; and it looks like very useful service. However when I was reading about it, it just felt like this is "just" a layer on top of &lt;a href="https://aws.amazon.com/iot-core/" rel="noopener noreferrer"&gt;IoT Core&lt;/a&gt; which exists for many years. Let's find out if this is the case...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API Gateway Websockets?&lt;/strong&gt;&lt;br&gt;
A while ago I compared &lt;a href="https://dev.to/slootjes/api-gateway-websockets-vs-iot-core-1me5"&gt;API Gateway Websockets with IoT Core&lt;/a&gt;. It might be very useful to also give it a read if you are exploring options for bi-directional communication on AWS.&lt;/p&gt;


&lt;h2&gt;
  
  
  Connection Management
&lt;/h2&gt;

&lt;p&gt;Both services do their own connection management. There is absolutely no need to store who connected and disconnect yourself or do any garbage collection.&lt;/p&gt;
&lt;h3&gt;
  
  
  AppSync Events
&lt;/h3&gt;

&lt;p&gt;AppSync Events is able to act on connects but &lt;em&gt;only for deciding if a client is allowed to connect&lt;/em&gt;. It does not seem possible to have any interaction with the outside world (ie: databases, AWS services, APIs).&lt;/p&gt;
&lt;h3&gt;
  
  
  IoT Core
&lt;/h3&gt;

&lt;p&gt;With IoT Core you are able act on connects and disconnect &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/life-cycle-events.html" rel="noopener noreferrer"&gt;using Lifecycle Events&lt;/a&gt; and have total freedom of doing something with this data.&lt;/p&gt;
&lt;h2&gt;
  
  
  Messaging
&lt;/h2&gt;

&lt;p&gt;Both services use a structure based on segments separated by slashes to create a hierarchy of your own choice, for example: &lt;code&gt;news/gaming&lt;/code&gt;, &lt;code&gt;news/sports&lt;/code&gt; or a level deeper with &lt;code&gt;news/sports/f1&lt;/code&gt; and &lt;code&gt;news/sports/soccer&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  AppSync Events
&lt;/h3&gt;

&lt;p&gt;In AppSync Events this is called called &lt;a href="https://docs.aws.amazon.com/appsync/latest/eventapi/channel-namespaces.html" rel="noopener noreferrer"&gt;"channel namespaces"&lt;/a&gt;. A wildcard &lt;em&gt;*&lt;/em&gt; is supported to receive messages from all sports using &lt;code&gt;news/sports/*&lt;/code&gt; or all news events using &lt;code&gt;news/*&lt;/code&gt;. There is a maximum of 5 segments (4 slashes).&lt;/p&gt;

&lt;p&gt;You are required to set up all channel names and configure them with an optional function that triggers on connect and when an event is published. It does not seem to be possible to use a Lambda for this.&lt;/p&gt;

&lt;p&gt;I was a bit surprised to find out that AppSync Events only uses the socket connection to &lt;em&gt;receive&lt;/em&gt; messages. To publish a message, you need to use an &lt;a href="https://docs.aws.amazon.com/appsync/latest/eventapi/publish-http.html" rel="noopener noreferrer"&gt;HTTP endpoint&lt;/a&gt;. While this is perfect for only broadcasting from server to client, this makes it a lot less suitable for use cases where your clients often need to communicate back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update April 2025&lt;/strong&gt;: AppSync Events now supports &lt;a href="https://docs.aws.amazon.com/appsync/latest/eventapi/publish-websocket.html" rel="noopener noreferrer"&gt;publishing over websockets&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  IoT Core
&lt;/h3&gt;

&lt;p&gt;As IoT Core is an MQTT Broker, it uses &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/topics.html" rel="noopener noreferrer"&gt;"topics"&lt;/a&gt; which are similar to the channel namespaces from AppSync Events. &lt;/p&gt;

&lt;p&gt;You don't need to manage topics. You can just subscribe and publish to any topic on the fly. The only restriction your clients have are the IAM permissions you give them. If a client does something now allowed by it's policy, it will be disconnected. There is a maximum of 8 segments (7 slashes).&lt;/p&gt;

&lt;p&gt;IoT Core is fully bi-directional, you can subscribe to topics, receive and publish messages over the same socket connection. Again, only if you allow this by the policy given to the client.&lt;/p&gt;
&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;
&lt;h3&gt;
  
  
  AppSync Events
&lt;/h3&gt;

&lt;p&gt;AppSync Events supports &lt;a href="https://docs.aws.amazon.com/appsync/latest/eventapi/configure-event-api-auth.html" rel="noopener noreferrer"&gt;multiple authentication types&lt;/a&gt; right out of the box with no or minimal development work required. A custom flow is possible with Lambda.&lt;/p&gt;
&lt;h3&gt;
  
  
  IoT Core
&lt;/h3&gt;

&lt;p&gt;Within IoT Core you can use a &lt;a href="https://dev.to/slootjes/aws-iot-core-simplified-part-2-presigned-url-4006"&gt;pre-signed url&lt;/a&gt;, a &lt;a href="https://dev.to/slootjes/aws-iot-core-simplified-part-3-custom-authorizer-4j5m"&gt;custom authorizer&lt;/a&gt; and/or &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/device-certs-create.html" rel="noopener noreferrer"&gt;certificates&lt;/a&gt; to manage client access. While this is not super difficult, it does require development and isn't as easy as AppSync Events.&lt;/p&gt;

&lt;p&gt;The upside thing is that &lt;em&gt;anything&lt;/em&gt; is possible. An &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html" rel="noopener noreferrer"&gt;IAM policy&lt;/a&gt; allows you to &lt;a href="https://dev.to/slootjes/aws-iot-core-simplified-part-1-permissions-k4d"&gt;decide exactly which client can do what on your topics&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Event Handling
&lt;/h2&gt;
&lt;h3&gt;
  
  
  AppSync Events
&lt;/h3&gt;

&lt;p&gt;AppSync Events allows you to provide an optional &lt;a href="https://docs.aws.amazon.com/appsync/latest/eventapi/channel-namespace-handlers.html" rel="noopener noreferrer"&gt;handler&lt;/a&gt; for a channel namespace:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Event handlers are functions that run on &lt;em&gt;AWS AppSync's JavaScript runtime&lt;/em&gt; and enable you to run custom business logic. You can use an event handler to process published events or process and authorize subscribe requests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is &lt;em&gt;very&lt;/em&gt; limited as it does not seem to be possible to use any AWS services, connect to a database or (third party) API. The only kind of integration that it supports is where AppSync Events is called by other services to publish a message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update April 2025&lt;/strong&gt;: AppSync Events now offers several integrations (called data channels) &lt;a href="https://docs.aws.amazon.com/appsync/latest/eventapi/supported-datasources.html" rel="noopener noreferrer"&gt;like Lambda, DynamoDB and EventBridge&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  IoT Core
&lt;/h3&gt;

&lt;p&gt;In IoT Core on the other hand, you can create SQL like &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-rules.html" rel="noopener noreferrer"&gt;rules&lt;/a&gt; to capture messages, send them to an AWS service of choice (including Lambda) using &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-rule-actions.html" rel="noopener noreferrer"&gt;actions&lt;/a&gt; to process them. This allows you to do virtually anything with the data that is sent from both clients and/or servers and also in any language of choice as long as it is supported by Lambda. Read this article if you want to learn more about &lt;a href="https://dev.to/slootjes/aws-iot-core-simplified-part-4-rules-m0a"&gt;Rules&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT color AS rgb FROM 'topic/subtopic' WHERE temperature &amp;gt; 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These queries are &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-sql-reference.html" rel="noopener noreferrer"&gt;extremely powerful&lt;/a&gt; and allow data transformation and filtering.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;Both services have a generous free tier in the first 12 months but eventually you will have to pay for something of course.&lt;/p&gt;

&lt;h3&gt;
  
  
  AppSync Events
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;$0.08 per million connection minutes.&lt;/li&gt;
&lt;li&gt;$1.00 per million Event API operations. All inbound messages published, outbound messages broadcast, event handlers invoked, and WebSockets operations, like client connection, subscription requests, and ping requests, are considered operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full details and examples &lt;a href="https://aws.amazon.com/appsync/pricing/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  IoT Core
&lt;/h3&gt;

&lt;p&gt;IoT Core has more features, I will only list the ones relevant for this comparison (on us-west-1 / eu-west-1 regions).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$0.08 per million connection minutes.&lt;/li&gt;
&lt;li&gt;$1.00 per million messages (first 1 billion)

&lt;ul&gt;
&lt;li&gt;$0.80 per million messages (4 to 5 billion)&lt;/li&gt;
&lt;li&gt;$0.70 per million messages (5+ billion)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;$0.15 per million rules triggered.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Full details and examples &lt;a href="https://aws.amazon.com/iot-core/pricing/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As you can see, the pricing is &lt;em&gt;very&lt;/em&gt; similar where IoT Core will be a bit cheaper since in AppSync Events as the rules engine is a lot cheaper and you will get a discount when having more than 4 billion messages (which a lot though if you ask me).&lt;/p&gt;

&lt;h2&gt;
  
  
  Service Quotas
&lt;/h2&gt;

&lt;p&gt;The service quotas for &lt;a href="https://docs.aws.amazon.com/general/latest/gr/appsync.html#limits_appsync" rel="noopener noreferrer"&gt;AppSync Events&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/general/latest/gr/iot-core.html#message-broker-limits" rel="noopener noreferrer"&gt;IoT Core&lt;/a&gt; have a few notable differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AppSync Events has a maximum message size of &lt;em&gt;1.2 Megabytes&lt;/em&gt; where IoT Core can do only &lt;em&gt;128 Kilobytes&lt;/em&gt; (hard limit).&lt;/li&gt;
&lt;li&gt;AppSync Events has an outbound messages limit of 1.000.000 per second where IoT Core has a default (soft) limit of 20.000.&lt;/li&gt;
&lt;li&gt;AppSync can do 2.000 connects per second, IoT core has a default (soft) limit of 500 per second.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall it seems that AppSync Events is way more generous with it's limits than IoT Core which makes it more suitable for a situations where you primarily want to broadcast to your clients. I had to adjust some of the IoT Core limits for a project and this was not a problem at all but it's always a hassle to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure As Code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  AppSync Events
&lt;/h3&gt;

&lt;p&gt;With AppSync Events you are able to &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-api.html" rel="noopener noreferrer"&gt;create multiple AppSync Events API&lt;/a&gt; instanced which could very useful for separation of environments or different services.&lt;/p&gt;

&lt;h3&gt;
  
  
  IoT Core
&lt;/h3&gt;

&lt;p&gt;Every account only has a single and default IoT Core instance at time of writing which you don't need to create manually. If you have multiple applications on the same account, you would need to separate access using topic prefixes or IAM policies.&lt;/p&gt;

&lt;p&gt;The endpoint for it is not available in CloudFormation, you would have to use a &lt;a href="https://stackoverflow.com/questions/44588863/how-to-obtain-aws-iot-endpoint-url-from-within-a-cloudformation-template" rel="noopener noreferrer"&gt;custom resource with a Lambda&lt;/a&gt; to retrieve it, quite cumbersome. Other than that, it is &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_IoT.html" rel="noopener noreferrer"&gt;very well supported&lt;/a&gt; within CloudFormation.&lt;/p&gt;

&lt;h2&gt;
  
  
  WAF
&lt;/h2&gt;

&lt;h3&gt;
  
  
  AppSync Events
&lt;/h3&gt;

&lt;p&gt;AppSync Events is &lt;a href="https://docs.aws.amazon.com/appsync/latest/eventapi/using-waf-protect-apis.html" rel="noopener noreferrer"&gt;supported by the AWS WAF&lt;/a&gt;. As far as I know, WAF does not support websockets and therefor most likely only the publish endpoint is protected.&lt;/p&gt;

&lt;h3&gt;
  
  
  IoT Core
&lt;/h3&gt;

&lt;p&gt;IoT Core is not supported by the AWS WAF for the simple reason that there are no account level HTTP endpoints to protect for this service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  AppSync Events
&lt;/h3&gt;

&lt;p&gt;This service is very straightforward and doesn't offer much other functionality then previously described.&lt;/p&gt;

&lt;h3&gt;
  
  
  IoT Core
&lt;/h3&gt;

&lt;p&gt;IoT Core has many more features which can clearly be seen when looking at the difference in &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/what-is-aws-iot.html" rel="noopener noreferrer"&gt;amount of documentation&lt;/a&gt; compared to &lt;a href="https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-welcome.html" rel="noopener noreferrer"&gt;AppSync Events&lt;/a&gt;. I don't want to pretend I have used all those features, I just want to highlight a few of them:&lt;/p&gt;

&lt;h4&gt;
  
  
  MQTT
&lt;/h4&gt;

&lt;p&gt;Since IoT Core is a fully managed MQTT broker, it supports the following protocols: MQTT, HTTPS, MQTT over websockets and LoRaWAN. This can be very useful if you have different types of clients. When working with hardware that used MQTT, it's very useful to create a web version that emulates the hardware using the same topics and messages but instead doing it over websockets.&lt;/p&gt;

&lt;h4&gt;
  
  
  Retained Messages
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html#mqtt-retain" rel="noopener noreferrer"&gt;Retained Messages&lt;/a&gt; is an MQTT feature supported by IoT Core where you can publish a message which the client receives when it gets back online. As of now, I could not find the possibility of doing this with AppSync Events.&lt;/p&gt;

&lt;h4&gt;
  
  
  Device Registry
&lt;/h4&gt;

&lt;p&gt;In IoT Core there is a concept called &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-thing-management.html" rel="noopener noreferrer"&gt;"Things"&lt;/a&gt;. This &lt;em&gt;optional&lt;/em&gt; feature allows you to keep a registry of all your things/devices. You can group them and even created "shadows" for them. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Shadows provide a reliable data store for devices, apps, and other cloud services to share data. They enable devices, apps, and other cloud services to connect and disconnect without losing a device's state.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyxzoaipiyqbo6gn84cb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyxzoaipiyqbo6gn84cb.jpg" alt="Computer server with small client computers around it" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;My take on the AppSync Events service is that it is most likely added to the portfolio to make it easier to implement push messaging from server to client which focuses on web and app development.&lt;/p&gt;

&lt;p&gt;In general it offers a lot less functionality than IoT Core as it focuses primarily on publishing messages to clients and not the other way around. It is however (a lot) easier to get started with and part of the &lt;a href="https://docs.aws.amazon.com/appsync/latest/eventapi/build-amplify-app.html" rel="noopener noreferrer"&gt;Amplify&lt;/a&gt; suite of services. Because of this, I do think that AppSync Events could become a service that will be embraced by many developers. Personally I do see some use cases where I would prefer AppSync Events over IoT Core, especially because of the more generous default quotas.&lt;/p&gt;

&lt;p&gt;IoT Core has way more features but has a steeper learning curve and seems more aimed at hardware &lt;em&gt;at first glance&lt;/em&gt;. This however doesn't mean it can not be used for web and app. If you are interested in using IoT Core for web and app "the easy way", &lt;a href="https://dev.to/slootjes/aws-iot-core-simplified-part-1-permissions-k4d"&gt;read this series of articles to get started&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update April 2025&lt;/strong&gt;: Now that AppSync Events supports bi-directional communication and integrations with other services like Lambda, DynamoDB and EventBridge the service got a lot more interesting. Personally I still prefer IoT Core as it is still more flexible since you do not need to define your channel namespaces upfront and the policies of IoT Core seem to be more fine grained.&lt;/p&gt;

&lt;h3&gt;
  
  
  AppSync Events powered by IoT Core?
&lt;/h3&gt;

&lt;p&gt;Looking at the different services I feel that AppSync Events technically &lt;em&gt;could&lt;/em&gt; very well be a service that is built on top of IoT Core. However, I'm not so sure anymore looking at the absence of bi-directional communication and the very different service quotas. Maybe an AWS employee could shine a light on this?&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>mqtt</category>
      <category>webdev</category>
    </item>
    <item>
      <title>AWS IoT Core Simplified - Part 4: Rules</title>
      <dc:creator>Robert Slootjes</dc:creator>
      <pubDate>Fri, 18 Oct 2024 09:58:38 +0000</pubDate>
      <link>https://dev.to/slootjes/aws-iot-core-simplified-part-4-rules-m0a</link>
      <guid>https://dev.to/slootjes/aws-iot-core-simplified-part-4-rules-m0a</guid>
      <description>&lt;p&gt;We are now able to connect clients to the server, either using a presigned url or a custom authorizer. Clients can send messages to each other but in many cases you want to do something with these messages server side. This can be done with &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-rules.html" rel="noopener noreferrer"&gt;rules&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Queries &amp;amp; Actions
&lt;/h2&gt;

&lt;p&gt;A rule basically consists of 2 parts; first, a query to capture data. Queries use an SQL-like syntax which offer a lot of flexibility. Second, actions can send data to services like Lambda, SQS, DynamoDB, Kinesis and many more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule Example
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-create-rule.html" rel="noopener noreferrer"&gt;Creating a rule&lt;/a&gt; is not difficult but there are a few pointers. Let's look at this example to get started:&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;SensorUpdatesRule&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::IoT::TopicRule&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;RuleName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-prod-sensor-updates&lt;/span&gt;
    &lt;span class="na"&gt;TopicRulePayload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;FROM&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'updates/sensor/+'"&lt;/span&gt;
      &lt;span class="na"&gt;AwsIotSqlVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2016-03-23'&lt;/span&gt;
      &lt;span class="na"&gt;Actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Sqs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;QueueUrl&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;SensorEvents.QueueUrl&lt;/span&gt;
            &lt;span class="na"&gt;RoleArn&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;RuleRole.Arn&lt;/span&gt;
            &lt;span class="na"&gt;UseBase64&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will capture &lt;em&gt;all&lt;/em&gt; messages published to wildcard topic &lt;code&gt;updates/sensor/+&lt;/code&gt; and sends them to an SQS queue. As we learned before, this includes topics like &lt;code&gt;updates/sensor/room-1&lt;/code&gt; and &lt;code&gt;updates/sensor/room-2&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissions
&lt;/h3&gt;

&lt;p&gt;As always, this rule needs the permissions to perform the configured actions, so let's add this:&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;RuleRole&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::IAM::Role&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;RoleName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-prod-iot-rule&lt;/span&gt;
    &lt;span class="na"&gt;AssumeRolePolicyDocument&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="s1"&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="na"&gt;Service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;iot.amazonaws.com&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;sts:AssumeRole&lt;/span&gt;
    &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;PolicyName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-prod-iot-rule-policy&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="s1"&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;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&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="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;SensorEvents.Arn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Queue
&lt;/h3&gt;

&lt;p&gt;And obviously we also need the queue we want to write to:&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;SensorEvents&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;QueueName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-prod-sensor-updates&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can create a Lambda and add it as a trigger to the queue. You can also trigger a Lambda directly from the rule but I personally like to add a queue in between so I have more visibility and control over it (ie: batch size, concurrency, dead letter queue). If triggering Lambda directly has your preference, make sure to give your rule lambda:invoke permissions instead.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Queries
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-sql-reference.html" rel="noopener noreferrer"&gt;Queries&lt;/a&gt; are the most important part of a rule and it's very feature rich.&lt;/p&gt;

&lt;h3&gt;
  
  
  SELECT
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-sql-select.html" rel="noopener noreferrer"&gt;SELECT&lt;/a&gt; is always required and defines which data you want to capture.&lt;/p&gt;

&lt;p&gt;Capture everything&lt;br&gt;
&lt;code&gt;SELECT *&lt;/code&gt; -&amp;gt; &lt;code&gt;{"temperature":21, "moisture": 50}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Select a specific field&lt;br&gt;
&lt;code&gt;SELECT temperature&lt;/code&gt; -&amp;gt; &lt;code&gt;{"temperature":21}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Select multiple fields&lt;br&gt;
&lt;code&gt;SELECT temperature, moisture&lt;/code&gt; -&amp;gt; &lt;code&gt;{"temperature":21, "moisture": 50}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Alias fields&lt;br&gt;
&lt;code&gt;SELECT temperature as temperature_celsius, moisture&lt;/code&gt; -&amp;gt; &lt;code&gt;{"temperature_celsius":21, "moisture": 50}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Hard coded values&lt;br&gt;
&lt;code&gt;SELECT *, 1 as version&lt;/code&gt; -&amp;gt; &lt;code&gt;{"temperature":21, "moisture": 50, "version": 1}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Single value&lt;br&gt;
&lt;code&gt;SELECT VALUE temperature&lt;/code&gt; -&amp;gt; &lt;code&gt;21&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;There are also many &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-sql-functions.html" rel="noopener noreferrer"&gt;functions&lt;/a&gt; to transform your data in your query. I use this for instance to get the client ID and timestamp:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT *, clientid() as clientId, timestamp() as timestamp&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Another notable function is &lt;em&gt;topic()&lt;/em&gt; which can be used to capture a segment of the topic as a variable:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT *, topic(3) as room FROM 'updates/sensor/room-1'&lt;/code&gt;&lt;br&gt;
-&amp;gt;&lt;br&gt;
&lt;code&gt;{"temperature":21, "moisture": 50, "room": "room-1"}&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  FROM
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-sql-from.html" rel="noopener noreferrer"&gt;FROM&lt;/a&gt; part defines on which topic(s) the query should trigger.&lt;/p&gt;

&lt;p&gt;Single topic&lt;br&gt;
&lt;code&gt;updates/sensor/room-1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Wildcard topic&lt;br&gt;
&lt;code&gt;updates/sensor/+&lt;/code&gt; or &lt;code&gt;updates/sensor/#&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It's also possible to capture &lt;em&gt;everything&lt;/em&gt; using a top level wildcard: &lt;code&gt;#&lt;/code&gt;. This could be useful for analytics or debugging purposes but depending on how much data your clients are sending it could generate quite some data (and cost).&lt;/p&gt;

&lt;h3&gt;
  
  
  WHERE
&lt;/h3&gt;

&lt;p&gt;The WHERE part is &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-sql-where.html" rel="noopener noreferrer"&gt;optional&lt;/a&gt; and allows you to narrow down which messages are captured. It's better to add the WHERE clause here than to discard the data in your Lambda function as it would simply save cost since the action won't run and the Lambda doesn't need to be executed.&lt;/p&gt;

&lt;p&gt;Only capture high temperatures&lt;br&gt;
&lt;code&gt;SELECT * FROM 'updates/sensor/+' WHERE temperature &amp;gt; 25&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;High temperatures with low moisture&lt;br&gt;
&lt;code&gt;SELECT * FROM 'updates/sensor/+' WHERE temperature &amp;gt; 25 AND moisture &amp;lt; 40&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;All the basic &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-sql-operators.html" rel="noopener noreferrer"&gt;operators&lt;/a&gt; you expect are supported out of the box.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Actions
&lt;/h2&gt;

&lt;p&gt;Once you have queried the messages you want, you want to process them. In the example I've showed SQS as action but there are &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-rule-actions.html" rel="noopener noreferrer"&gt;many more&lt;/a&gt; to choose from. You can for instance write directly to DynamoDB to query your data or log to CloudWatch to create nice metrics. Something I personally do is to write my events to a Firehose stream so I can easily log chunks of data to S3 per minute. Remember to always give your role the right permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Saving money with Basic Ingest
&lt;/h2&gt;

&lt;p&gt;A notable feature which could save you some money is &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-basic-ingest.html" rel="noopener noreferrer"&gt;basic ingest&lt;/a&gt;. With basic ingest you can let your clients send messages to the rule &lt;em&gt;directly&lt;/em&gt; instead of sending it to the topic the rule is created for. A rule will create its own topic in this format: &lt;code&gt;$aws/rules/{rule_name}&lt;/code&gt;. Sending messages to a rule directly instead of a topic saves you the cost of a message, depending on the amount of messages this could be very beneficial. The downside of this method is that other clients can not subscribe to it. Also make sure your client has permissions to publish to this topic, otherwise it will disconnect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;With this series of posts I hope to made it clear that IoT Core isn't as difficult as it seems. While IoT Core is very cheap (and has a generous free tier in the first 12 months!) there are obviously &lt;a href="https://aws.amazon.com/iot-core/pricing/" rel="noopener noreferrer"&gt;costs involved&lt;/a&gt;. IoT Core is however very cheap so fiddling around with it doesn't break the bank. Let me know in the comments what type of application you've built with it to inspire me and others.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>mqtt</category>
      <category>aws</category>
      <category>iot</category>
    </item>
    <item>
      <title>AWS IoT Core Simplified - Part 3: Custom Authorizer</title>
      <dc:creator>Robert Slootjes</dc:creator>
      <pubDate>Mon, 01 Jul 2024 08:54:17 +0000</pubDate>
      <link>https://dev.to/slootjes/aws-iot-core-simplified-part-3-custom-authorizer-4j5m</link>
      <guid>https://dev.to/slootjes/aws-iot-core-simplified-part-3-custom-authorizer-4j5m</guid>
      <description>&lt;h2&gt;
  
  
  Custom Authorizer
&lt;/h2&gt;

&lt;p&gt;While the Presigned URL method can be very effective, sometimes you need a little bit more control. This can be done with a &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/config-custom-auth.html" rel="noopener noreferrer"&gt;custom authorizer&lt;/a&gt;. Simply said, when a custom authorizer is used, IoT Core will invoke a Lambda function which needs to approve or deny the connection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating
&lt;/h3&gt;

&lt;p&gt;It's quite easy to create an authorizer with CloudFormation:&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;IoTAuthorizer&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::IoT::Authorizer&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;AuthorizerFunctionArn&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;IotAuthorizerLambdaFunction.Arn&lt;/span&gt;
    &lt;span class="na"&gt;AuthorizerName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-prod-custom&lt;/span&gt;
    &lt;span class="na"&gt;SigningDisabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;
    &lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ACTIVE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will need to give it a &lt;em&gt;unique&lt;/em&gt; name (this part is very important!) and point to a Lambda function that will receive the connection request and needs to decide if the client is allowed to connect, and with which permissions. I recommend to prefix with the name of the stack and the stage to be sure the name is always unique. We disable token signing as we do not need it for this use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissions
&lt;/h3&gt;

&lt;p&gt;Make sure your authorizer has permission to invoke your Lambda function.&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;IoTAuthorizerPolicy&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::Lambda::Permission&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;FunctionName&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;IotAuthorizerLambdaFunction.Arn&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;lambda:InvokeFunction&lt;/span&gt;
    &lt;span class="na"&gt;Principal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;iot.amazonaws.com&lt;/span&gt;
    &lt;span class="na"&gt;SourceArn&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;IoTAuthorizer.Arn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this permission, your Lambda function can not be invoked by IoT Core and your client won't be able to connect. This is something you can easily forget and it's quite a pain to figure out why it's not working.&lt;/p&gt;

&lt;p&gt;Now, IoT Core knows which custom authorizer to use when connecting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting
&lt;/h2&gt;

&lt;p&gt;MQTT clients have the option to provide a &lt;em&gt;username&lt;/em&gt; and &lt;em&gt;password&lt;/em&gt; when connecting, you will receive these in your custom authorizer. &lt;/p&gt;

&lt;h3&gt;
  
  
  Passing the authorizer
&lt;/h3&gt;

&lt;p&gt;When connecting to IoT Core, you need to send along the &lt;em&gt;name&lt;/em&gt; of the custom auhtorizer you want to use. With a websocket connection, this is a query string variable as part of the url:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wss://.../mqtt?x-amz-customauthorizer-name={authorizer}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's have a look at the Paho MQTT JS library again:&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wss://xxxxxxxx-ats.iot.eu-west-1.amazonaws.com/mqtt?x-amz-customauthorizer-name={authorizer}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Paho&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MQTT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;the_client_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;useSSL&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;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;mqttVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="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="s2"&gt;the_username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;the_password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connected!&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;onFailure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connecting by host
&lt;/h3&gt;

&lt;p&gt;It is also possible to connect by host (&lt;em&gt;xxxxxxxx-ats.iot.eu-west-1.amazonaws.com&lt;/em&gt;) and port instead of using the websocket url. Make sure to use port &lt;em&gt;443&lt;/em&gt; and then add the authorizer to the username like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{username}?x-amz-customauthorizer-name={authorizer}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows you to use "regular" MQTT clients like MQTT.fx to connect as well. For you as developer there is no difference in how the connection is handled, which is good news.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request
&lt;/h2&gt;

&lt;p&gt;When connecting with the above connection details, the event from IoT Core in your custom authorizer Lambda looks something like this:&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;"protocolData"&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;"mqtt"&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;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"the_username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dGhlX3Bhc3N3b3Jk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"clientId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"the_client_id"&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;"protocols"&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="s2"&gt;"mqtt"&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;"signatureVerified"&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;"connectionMetadata"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"c979e717-32c4-7309-b46b-56ab0c5e36b4"&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;As you can see, it contains the client ID, username and password that was sent when connecting. The password however is base64 encoded so make sure to decode it before using it. Please note that the username and password can be empty as they are not mandatory.&lt;/p&gt;

&lt;p&gt;When connecting over a websocket connection you will also receive the query string and headers that were part of the request in an "https" block in the "protocolData". Personally I'm sticking to the username and password since it will work for both websocket clients and other MQTT clients that use the hostname and port.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Response
&lt;/h2&gt;

&lt;p&gt;The response structure for a custom authorizer needs to look like this:&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;"isAuthenticated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"principalId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"disconnectAfterInSeconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"refreshAfterInSeconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"policyDocuments"&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"Action"&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="s2"&gt;"iot:Connect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"iot:Subscribe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"iot:Receive"&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;"Resource"&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="s2"&gt;"arn:aws:iot:{region}:{account-id}:client/user1-*"&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:iot:{region}:{account-id}:topicfilter/updates/user/user1"&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:iot:{region}:{account-id}:topic/updates/user/user1"&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;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;The most important keys are "isAuthenticated" and "policyDocuments". &lt;/p&gt;

&lt;p&gt;"isAuthenticated" let's IoT Core know if the client is allowed to connect. If this is &lt;em&gt;false&lt;/em&gt;, the connection is refused. If it's &lt;em&gt;true&lt;/em&gt;, the connection is allowed and the policy is applied to the connection. In this example, we allow a client to connect with clientId wildcard of "user1-*" and to subscribe and receive messages on topic "updates/user/1". The principalId is only allowed to contain a-z, A-Z, 0-9 with a maximum length of 128.&lt;/p&gt;

&lt;p&gt;An example custom authorizer Lambda:&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;IoTCustomAuthorizerEvent&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;aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;IoTCustomAuthorizerResult&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;aws-lambda/trigger/iot-authorizer.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handle&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;IoTCustomAuthorizerEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IoTCustomAuthorizerResult&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isAuthenticated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;statement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;protocolData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;mqtt&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// authenticate any way you like&lt;/span&gt;
    &lt;span class="nx"&gt;isAuthenticated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;statement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;Action&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;IoT:Connect&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;IoT:Subscribe&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;IoT:Receive&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;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;// allow client to connect with "{username}-{something}&lt;/span&gt;
        &lt;span class="s2"&gt;`arn:aws:iot:*:*:client/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-*`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// allow client to subscribe to "updates/user/{username}&lt;/span&gt;
        &lt;span class="s2"&gt;`arn:aws:iot:*:*:topicfilter/updates/user/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// allow client to receive messages from "updates/user/{username}&lt;/span&gt;
        &lt;span class="s2"&gt;`arn:aws:iot:*:*:topic/updates/user/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;principalId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;empty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/ug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;disconnectAfterInSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 24 hours&lt;/span&gt;
    &lt;span class="na"&gt;refreshAfterInSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 1 hour&lt;/span&gt;
    &lt;span class="na"&gt;policyDocuments&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;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2012-10-17&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;statement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Since this article focuses on IoT Core, I won't cover the authentication process in the example code. Obviously this is not production ready code and there are many ways how you can authenticate your client credentials, to name a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Look up the username in DynamoDB (or other database of choice) and verify the password (make sure to use something like bcrypt for storing and comparing passwords!)&lt;/li&gt;
&lt;li&gt;Pass a &lt;a href="https://jwt.io/" rel="noopener noreferrer"&gt;Json Web Token&lt;/a&gt; (access or id token from Cognito or other identity provider) as the password so the Lambda only needs to verify the token and then use what's inside (ie: the subject/user ID).&lt;/li&gt;
&lt;li&gt;Call an external API to verify the credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The great thing is that you can use kind of any method for it. You receive the credentials, verify them &lt;em&gt;and&lt;/em&gt; define exactly what the client can do. You can also set up multiple custom authorizers with their own type of authentication. It is a bit more work than the presigned url but this is more flexible if you have clients that need different permissions.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Debugging
&lt;/h2&gt;

&lt;p&gt;Sometimes you might not really be sure why your client is not able to connect. Luckily IoT Core allows you to enable logging. At the IoT Core setting page, go to "Manage Logs" and choose the log level you want. When developing the &lt;em&gt;Debug&lt;/em&gt; level will be the most helpful. Don't forget to disable the logger once you put something in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common issues
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;IoT Core doesn't have permission to invoke your Lambda function&lt;/li&gt;
&lt;li&gt;The name of the authorizer has a typo in it&lt;/li&gt;
&lt;li&gt;The password wasn't base64 decoded before using it&lt;/li&gt;
&lt;li&gt;The principal ID contains invalid characters&lt;/li&gt;
&lt;li&gt;The policy doesn't allow the client ID to connect&lt;/li&gt;
&lt;li&gt;The response from your Lambda is malformed in other ways&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Up Next
&lt;/h2&gt;

&lt;p&gt;In the next part of the series I will explain how you can leverage rules to do something with the messages sent to the server.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>iot</category>
      <category>mqtt</category>
      <category>serverless</category>
    </item>
    <item>
      <title>AWS IoT Core Simplified - Part 2: Presigned URL</title>
      <dc:creator>Robert Slootjes</dc:creator>
      <pubDate>Thu, 13 Jun 2024 06:25:48 +0000</pubDate>
      <link>https://dev.to/slootjes/aws-iot-core-simplified-part-2-presigned-url-4006</link>
      <guid>https://dev.to/slootjes/aws-iot-core-simplified-part-2-presigned-url-4006</guid>
      <description>&lt;p&gt;This is part 2 in a series of articles about IoT Core:&lt;/p&gt;

&lt;p&gt;Parts coming up:&lt;br&gt;
Part 3: Connect using a custom authorizer&lt;br&gt;
Part 4: Topic Rules&lt;/p&gt;
&lt;h2&gt;
  
  
  Connect using a presigned url
&lt;/h2&gt;

&lt;p&gt;Similar to S3, it's possible to create a presigned url for IoT Core. You can even cache and reuse this presigned url for multiple clients as long as the client id is different per client. If you connect a client with a client id that is already in use, the other client will be disconnected. This can be a great method for pushing content to clients but also allows them to publish things themselves if your use case requires it. While this is a super easy method, it isn't the most flexible way of using it in terms of permissions. Please note that this only works for websocket urls, not for regular MQTT connections.&lt;/p&gt;
&lt;h2&gt;
  
  
  Permissions
&lt;/h2&gt;

&lt;p&gt;It's important to realize that the presigned url has the same permissions as the role that was used to sign it with. That means that &lt;em&gt;all clients will have the exact same permissions&lt;/em&gt; unless you specifically create and use a different role per use case. For example, if your Lambda has a role that allows connecting with any client ID on any topic, every client will be able to connect to any topic. In some cases this is totally fine but make sure this is OK for your use case. Obviously, you can (and you should) restrict your Lambda to only allow what you want your clients to do.&lt;/p&gt;

&lt;p&gt;For instance, if you have a website where you want to be able to push live news updates to an end user, you can have a policy with this in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Connect",
        "iot:Subscribe",
        "iot:Receive"
      ],
      "Resource": [
        "arn:aws:iot:{region}:{account-id}:client/user-*",
        "arn:aws:iot:{region}:{account-id}:topicfilter/news",
        "arn:aws:iot:{region}:{account-id}:topic/news"
        ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will allow the client to connect with any client ID as long as it starts with &lt;em&gt;user-&lt;/em&gt; (you could generate a uuid to make it random), and will allow to subscribe to the "news" topic and receive messages over it. This is now basically a read only connect as the client isn't allowed to publish any messages with this policy.&lt;/p&gt;

&lt;p&gt;You can have a separate role for your publishers that allows to write updates to the topic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Connect",
        "iot:Publish"
      ],
      "Resource": [
        "arn:aws:iot:{region}:{account-id}:client/publisher-*",
        "arn:aws:iot:{region}:{account-id}:topic/news"
      ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, you can of also &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/iot-data-plane/command/PublishCommand/" rel="noopener noreferrer"&gt;publish a message using the AWS SDK&lt;/a&gt; instead of doing this with a client that is connected with MQTT. The &lt;em&gt;iot:Publish&lt;/em&gt; permission to the respective topic is still required obviously.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;This seems to be quite hidden in the documentation however I did find some &lt;a href="https://github.com/aws-samples/aws-iot-examples/blob/master/mqttSample/js/app.js" rel="noopener noreferrer"&gt;example code&lt;/a&gt;. I converted this into something that makes use of the current AWS utilities which makes it a lot more compact and readable. The code examples are written in Typescript, I recommend using &lt;a href="https://dev.to/slootjes/optimizing-typescript-packages-in-serverless-framework-with-esbuild-1ol4"&gt;Serverless Framework with esbuild&lt;/a&gt; to deploy the code so you can run it with a node runtime in Lambda.&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;crypto&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:crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BinaryLike&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Sha256&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;@aws-crypto/sha256-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AwsCredentialIdentity&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;@aws-sdk/types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SignatureV4&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;@smithy/signature-v4&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;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BinaryLike&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&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;toQueryString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryStrings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryStrings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getSignedUrl&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;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AwsCredentialIdentity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;expiresIn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iotdevicegateway&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;algorithm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS4-HMAC-SHA256&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;sigV4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SignatureV4&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Sha256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;credentials&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;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;:-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\.\d{3}&lt;/span&gt;&lt;span class="sr"&gt;/gu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credentialScope&lt;/span&gt; &lt;span class="o"&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;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&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;region&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;service&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/aws4_request`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Amz-Algorithm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;algorithm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Amz-Credential&lt;/span&gt;&lt;span class="dl"&gt;'&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="nf"&gt;encodeURIComponent&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;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessKeyId&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;credentialScope&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Amz-Date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Amz-Expires&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;expiresIn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Amz-SignedHeaders&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;host&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;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/mqtt&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;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`host:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n`&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;canonicalRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`GET\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;toQueryString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\nhost\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&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;stringToSign&lt;/span&gt; &lt;span class="o"&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;algorithm&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;credentialScope&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canonicalRequest&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;parameters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Amz-Signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sigV4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stringToSign&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessionToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Amz-Security-Token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessionToken&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="s2"&gt;`wss://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;toQueryString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;I can now create a Lambda with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyResultV2&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;aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getSignedUrl&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;../../Service/IoTCore.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handle&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;APIGatewayProxyResultV2&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="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getSignedUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_IOT_HOST&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_DEFAULT_REGION&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sessionToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SESSION_TOKEN&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and when calling it, I receive a URL I can use to connect with something like a Paho MQTT Client. For this example I've set a AWS_IOT_HOST environment variable with the endpoint of IoT Core (xxxxxxx-ats.iot.eu-west-1.amazonaws.com). The endpoint can be found by navigating to the IoT Core service in the dashboard and then going to Settings:&lt;/p&gt;

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

&lt;p&gt;You can of course also use the &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/iot/command/DescribeEndpointCommand/" rel="noopener noreferrer"&gt;IoT Core SDK&lt;/a&gt; to retrieve the endpoint.&lt;/p&gt;

&lt;h1&gt;
  
  
  Connecting
&lt;/h1&gt;

&lt;p&gt;Now that you have a way of getting a presigned url, you want to use it to connect to IoT Core. To do this from a browser, you can use the &lt;a href="https://github.com/eclipse/paho.mqtt.javascript" rel="noopener noreferrer"&gt;Paho MQTT library&lt;/a&gt; and following snippet of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Paho&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MQTT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&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;clientId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;useSSL&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;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;mqttVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connected&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;onFailure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failed to connect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onMessageArrived&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onConnectionLost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lost connection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The url is the response from your Lambda. Make sure the clientId is allowed by your policy as otherwise it will refuse to connect.&lt;/p&gt;

&lt;p&gt;You can subscribe to a topic like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updates/"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and publish a message like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&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;Paho&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MQTT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destinationName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updates/messages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note, once again, doing anything not allowed by your policy will result in a disconnect.&lt;/p&gt;

&lt;h1&gt;
  
  
  Caching
&lt;/h1&gt;

&lt;p&gt;Presigned urls from a Lambda only work for a limited time due to how IAM works but it's possible to cache it in a CDN like CloudFront for a couple of minutes. This way you do not need to generate a fresh url for every visitor. With my configuration, the presigned urls are valid for a maximum of 5 minutes. In practice, I cache them for 1 minute to be on the very safe side.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;You now have a basic way of working with IoT Core that allows for powerful bidirectional communication. Have fun!&lt;/p&gt;

&lt;p&gt;In part 3 I will explain how a custom authorizer can be used to do more fine grained permissions per client.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>iot</category>
      <category>mqtt</category>
      <category>serverless</category>
    </item>
    <item>
      <title>AWS IoT Core Simplified - Part 1: Permissions</title>
      <dc:creator>Robert Slootjes</dc:creator>
      <pubDate>Tue, 11 Jun 2024 18:46:17 +0000</pubDate>
      <link>https://dev.to/slootjes/aws-iot-core-simplified-part-1-permissions-k4d</link>
      <guid>https://dev.to/slootjes/aws-iot-core-simplified-part-1-permissions-k4d</guid>
      <description>&lt;p&gt;AWS IoT Core is an amazing (and often overlooked) service as I said before when &lt;a href="https://dev.to/slootjes/api-gateway-websockets-vs-iot-core-1me5"&gt;comparing IoT Core to API Gateway Websockets&lt;/a&gt;. To summarize, IoT Core manages it own connections, it has a powerful system of topics and rules, it scales well and, not unimportant, it's quite cheap.&lt;/p&gt;

&lt;p&gt;However, IoT Core can also be intimidating and &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/what-is-aws-iot.html" rel="noopener noreferrer"&gt;difficult&lt;/a&gt; to start with because of &lt;em&gt;things&lt;/em&gt;, &lt;em&gt;fleets&lt;/em&gt;, &lt;em&gt;shadow devices&lt;/em&gt; and &lt;em&gt;certificates&lt;/em&gt;. The good news is that those things are not at all mandatory to use this service. While maybe not obvious at first sight, there are ways to just connect some clients to IoT Core without first registering or managing them.&lt;/p&gt;

&lt;p&gt;I will be posting a few articles to cover basic usage of IoT Core aimed at developers who are looking for an easy and flexible way of bi-directional communication between clients and server using websockets.&lt;/p&gt;

&lt;p&gt;Part 1: Introduction &amp;amp; Permissions (this one!)&lt;br&gt;
Part 2: Connect using a presigned url&lt;br&gt;
Part 3: Connect using a custom authorizer&lt;br&gt;
Part 4: Topic Rules&lt;/p&gt;
&lt;h1&gt;
  
  
  What is IoT Core?
&lt;/h1&gt;

&lt;p&gt;Simply put, &lt;em&gt;and maybe not giving enough credit&lt;/em&gt;, IoT Core is an MQTT broker. MQTT is a lightweight publish-subscribe protocol. The MQTT broker acts as the central hub between connected clients that can send and reach messages to each other. Messages are sent over topics. A topic is a hierarchical string separated by slashes, for instance &lt;em&gt;sensor/temperature/room1&lt;/em&gt;. A client can send temperature updates to this topic every minute and another client might be subscribed to this topic and receives all temperatures that are sent to do something with it.&lt;/p&gt;

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

&lt;p&gt;MQTT has many advanced options and use cases that I won't cover here. If you want to know more about them I recommend reading more &lt;a href="https://www.hivemq.com/mqtt-essentials/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. For the rest of this series, it's enough to know that there is a central broker (IoT Core) and clients (ie: a web browser) that can publish and receive messages.&lt;/p&gt;
&lt;h2&gt;
  
  
  Topic Wildcards
&lt;/h2&gt;

&lt;p&gt;In a &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/topics.html" rel="noopener noreferrer"&gt;topic&lt;/a&gt;, a &lt;em&gt;+&lt;/em&gt; character can be used as a wildcard for one level while a &lt;em&gt;#&lt;/em&gt; character can be used for a multi level wildcard. It is possible to have more than 1 single level wildcard, like &lt;em&gt;sensor/+/+&lt;/em&gt; however it's not possible to have more than 1 multi level wildcard (which makes sense).&lt;/p&gt;

&lt;p&gt;Looking at the previous example, if a client is interested in temperatures of all rooms, it can subscribe to &lt;em&gt;sensor/temperature/#&lt;/em&gt; where the # character is a wildcard in MQTT. This means that if a client wants the informations of &lt;em&gt;all&lt;/em&gt; sensors, it can listen to &lt;em&gt;sensor/#&lt;/em&gt;. It is also possible to subscribe to &lt;em&gt;sensor/+/room1&lt;/em&gt; to receive all messages (temperature and others) of room1. &lt;/p&gt;
&lt;h1&gt;
  
  
  Security
&lt;/h1&gt;

&lt;p&gt;For simple use of IoT Core, there are 4 permissions that you need to know  about. &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege" rel="noopener noreferrer"&gt;Applying least-privilege permissions&lt;/a&gt; obviously also applies to IoT Core. In case a client performs an action that is not allowed by it's policy, the client will be disconnected by the server.&lt;/p&gt;

&lt;p&gt;It's important to realize that &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/pub-sub-policy.html#pub-sub-policy-cert" rel="noopener noreferrer"&gt;MQTT wildcards are treated as literal characters in IAM permissions&lt;/a&gt;. This means that you can use the &lt;em&gt;?&lt;/em&gt;, &lt;em&gt;*&lt;/em&gt;, &lt;em&gt;+&lt;/em&gt; and &lt;em&gt;#&lt;/em&gt; in the IAM policy but the &lt;em&gt;?&lt;/em&gt; and &lt;em&gt;*&lt;/em&gt; will be treated only as wildcard for the IAM policy itself while &lt;em&gt;+&lt;/em&gt; and &lt;em&gt;#&lt;/em&gt; are treated as wildcards for topics in IoT Core. I will explain this using several examples.&lt;/p&gt;
&lt;h1&gt;
  
  
  Permissions
&lt;/h1&gt;

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

&lt;p&gt;First, your client needs to be able to connect to the server. The client ID must be &lt;em&gt;unique&lt;/em&gt; per region, otherwise the previously connected client with the same client ID will be disconnected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;arn:aws:iot:{region}:{account-id}:client/*&lt;/code&gt;&lt;br&gt;
Allows to connect using &lt;em&gt;any&lt;/em&gt; client ID.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;arn:aws:iot:{region}:{account-id}:client/sensor-123&lt;/code&gt;&lt;br&gt;
Allows to connect as &lt;em&gt;sensor-123&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;arn:aws:iot:{region}:{account-id}:client/sensor-???&lt;/code&gt;&lt;br&gt;
Allows to connect using any client ID as long as it starts with &lt;em&gt;sensor-&lt;/em&gt; and ends with 3 characters. This means that &lt;em&gt;sensor-123&lt;/em&gt;, &lt;em&gt;sensor-foo&lt;/em&gt; will work, but &lt;em&gt;sensor&lt;/em&gt;, &lt;em&gt;sensor-foobar&lt;/em&gt; and &lt;em&gt;sensor-123456&lt;/em&gt; won't work.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;arn:aws:iot:{region}:{account-id}:client/sensor-*&lt;/code&gt;&lt;br&gt;
Allows to connect using any client ID as long as it starts with &lt;em&gt;sensor-&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;arn:aws:iot:{region}:{account-id}:client/user-????????-????-????-????-????????????&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Allows to connect using &lt;em&gt;user-{uuid}&lt;/em&gt;.&lt;/p&gt;

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

&lt;p&gt;Before a client can receive messages, the client must first subscribe to one or multiple topics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;arn:aws:iot:{region}:{account-id}:topicfilter/updates&lt;/code&gt;&lt;br&gt;
Allows to subscribe to &lt;em&gt;updates&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;arn:aws:iot:{region}:{account-id}:topicfilter/updates/sensor-???&lt;/code&gt;&lt;br&gt;
Allows to subscribe to &lt;em&gt;updates/sensor-123&lt;/em&gt;, &lt;em&gt;updates/sensor-foo&lt;/em&gt; and other variants as long as the topic starts with &lt;em&gt;updates/&lt;/em&gt; and then has a second level starting with &lt;em&gt;sensor-&lt;/em&gt; and then 3 characters. It won't accept updates/foo-123 or "updates/sensor-123/foo".&lt;/p&gt;

&lt;p&gt;&lt;code&gt;arn:aws:iot:{region}:{account-id}:topicfilter/updates/sensor-*&lt;/code&gt;&lt;br&gt;
Allows to subscribe to &lt;em&gt;updates/sensor-123&lt;/em&gt;, &lt;em&gt;updates/sensor-123/foo&lt;/em&gt;, and/or "updates/sensor-123/foo/bar/baz" because the * allows for any level of depth in the topic.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;arn:aws:iot:{region}:{account-id}:topicfilter/updates/sensor-*/???&lt;/code&gt;&lt;br&gt;
Allows to subscribe to &lt;em&gt;updates/sensor-123/foo&lt;/em&gt;, &lt;em&gt;updates/sensor-12345678/bar&lt;/em&gt; and other variants as long as the topic starts with "updates/sensor-" and has a second level with exactly 3 characters. It won't accept &lt;em&gt;updates/sensor-1&lt;/em&gt; or &lt;em&gt;updates/sensor-12345/#&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;You can of course use the wildcards in MQTT too if you want.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;arn:aws:iot:{region}:{account-id}:topicfilter/updates/+&lt;/code&gt;&lt;br&gt;
Allows to subscribe to &lt;em&gt;topic/updates/+&lt;/em&gt; only as the &lt;strong&gt;+&lt;/strong&gt; is treated as a literal in the policy.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;arn:aws:iot:{region}:{account-id}:topicfilter/updates/#&lt;/code&gt;&lt;br&gt;
Allows to subscribe to &lt;em&gt;topic/updates/#&lt;/em&gt; only as the &lt;strong&gt;#&lt;/strong&gt; is treated as a literal in the policy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvmd09y4siw7n0k55rwyy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvmd09y4siw7n0k55rwyy.png" alt="Computer sending and receiving messages" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  iot:Receive &amp;amp; iot:Publish
&lt;/h2&gt;

&lt;p&gt;iot:Receive allows the client to receive messages over the topics that it is subscribed to while iot:Publish allows the client to publish messages that can be received by other clients and/or topic rules.&lt;/p&gt;

&lt;p&gt;The iot:Receive &amp;amp; iot:Publish permissions have the same structure as iot:Subscribe with the only difference that it uses "topic" instead of "topicfilter". Usually the iot:Subscribe and iot:Receive will be the same as it doesn't make sense to allow receiving on topics the client isn't allowed to subscribe on and vice versa.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;arn:aws:iot:{region}:{account-id}:topic/updates&lt;/code&gt;&lt;br&gt;
&lt;code&gt;arn:aws:iot:{region}:{account-id}:topic/updates/sensor-???&lt;/code&gt;&lt;br&gt;
&lt;code&gt;arn:aws:iot:{region}:{account-id}:topic/updates/sensor-*&lt;/code&gt;&lt;br&gt;
&lt;code&gt;arn:aws:iot:{region}:{account-id}:topic/updates/sensor-*/???&lt;/code&gt;&lt;br&gt;
&lt;code&gt;arn:aws:iot:{region}:{account-id}:topic/updates/+&lt;/code&gt;&lt;br&gt;
&lt;code&gt;arn:aws:iot:{region}:{account-id}:topic/updates/#&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Creating a policy
&lt;/h1&gt;

&lt;p&gt;Since the permissions and values have different formats, you can easily combine everything in a single policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Connect",
        "iot:Subscribe",
        "iot:Receive"
      ],
      "Resource": [
        "arn:aws:iot:{region}:{account-id}:client/sensor-*",
        "arn:aws:iot:{region}:{account-id}:topicfilter/sensor-*/???",
        "arn:aws:iot:{region}:{account-id}:topic/sensor-*/???"
        ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the next article I will explain how to connect to IoT Core using a presigned url.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>iot</category>
      <category>serverless</category>
      <category>mqtt</category>
    </item>
    <item>
      <title>Amazon EventBridge Event Envelope</title>
      <dc:creator>Robert Slootjes</dc:creator>
      <pubDate>Tue, 02 Jan 2024 10:41:55 +0000</pubDate>
      <link>https://dev.to/slootjes/amazon-eventbridge-event-metadata-1n80</link>
      <guid>https://dev.to/slootjes/amazon-eventbridge-event-metadata-1n80</guid>
      <description>&lt;p&gt;&lt;a href="https://aws.amazon.com/eventbridge/" rel="noopener noreferrer"&gt;Amazon EventBridge&lt;/a&gt; Event Bus is a serverless event bus that helps you receive, filter, transform, route, and deliver events. It's a very powerful service that allows developers to work with events without worrying about scaling and it offers a very flexible system of rules to send events to virtually any destination.&lt;/p&gt;

&lt;h1&gt;
  
  
  Standard
&lt;/h1&gt;

&lt;p&gt;A standard event in EventBridge looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "version": "0", // set by AWS
    "id": "9773f4b3-ff25-5ef2-b41b-791f868d424e", // set by AWS
    "detail-type": "your_event_type", // set by you
    "source": "your_app", // set by you
    "account": "123456789", // set by AWS
    "time": "2023-12-15T14:41:33Z", // set by you, optional
    "region": "eu-west-1", // set by AWS
    "resources": [], // set by you, optional
    "detail": {} // set by you, optional
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While having a source, type and time is a very good base for an event I personally like to add some more details. Occasionally multiple events are published in the same request and then it can be very useful to trace them back to see what happened in which order. Since EventBridge doesn't guarantee the order of events, it's not reliable to depend on the service to process everything in the right order.&lt;/p&gt;

&lt;h1&gt;
  
  
  With Metadata
&lt;/h1&gt;

&lt;p&gt;This example shows how I'm populating the detail key with some extra metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "version": "0", // always 0
    "id": "9773f4b3-ff25-5ef2-b41b-791f868d424e",
    "detail-type": "your_event_type",
    "source": "your_app",
    "account": "123456789",
    "time": "2023-12-15T14:41:33Z",
    "region": "eu-west-1",
    "resources": [],
    "detail": {
        "createdAt": 1702651293210,
        "correlationId": "2dc5e557-dcc9-4aef-9794-be207ca1a011",
        "sequenceNr": 1,
        "idempotencyKey": "custom_idempotency_key:123",
        "persist": true,
        "context": {
            "foo": "bar"
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, I'm adding a few things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;createdAt&lt;/strong&gt;&lt;br&gt;
Milisecond unix timestamp.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;correlationId&lt;/strong&gt;&lt;br&gt;
A uuid which is the same for all events published in the same request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sequenceNr&lt;/strong&gt;&lt;br&gt;
An increasing number for every event published in the same request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;idempotencyKey&lt;/strong&gt;&lt;br&gt;
A custom idempotency key so that if for some reason an event got published multiple times (EventBridge provides &lt;em&gt;at-least-once&lt;/em&gt; event delivery to targets). The consumer can ignore an event if it was previously handled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;persist&lt;/strong&gt; &lt;br&gt;
A boolean to indicate if I want to persist this event to S3 using a rule that sends it to Firehose. If you want to learn more, I recommend you to read this article: &lt;a href="https://dev.to/slootjes/logging-eventbridge-events-to-s3-with-firehose-2hkc"&gt;Logging EventBridge events to S3 with Firehose&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For some projects, I use the same rule to persist the events in DynamoDB so I can easily look up events by ID, correlation ID (sorted by the Sequence Nr) and event type (sorted by timestamp) using Global Secondary Indexes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;context&lt;/strong&gt;&lt;br&gt;
This is the data that belongs to the event, you can put here what ever you like.&lt;/p&gt;

&lt;h1&gt;
  
  
  Benefits
&lt;/h1&gt;

&lt;p&gt;Having a &lt;em&gt;correlationId&lt;/em&gt;, &lt;em&gt;sequenceNr&lt;/em&gt; and &lt;em&gt;createdAt&lt;/em&gt; allows you to see which events were published in the same request and in which order. The &lt;em&gt;idempotencyKey&lt;/em&gt; adds reliability to the application as it can prevent to do double work (depending on the type of event, this can have quite some impact).&lt;/p&gt;

&lt;p&gt;Persisting events can be very useful. It's super useful for debugging but also a gold mine for auditing and analytical purposes.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>eventdriven</category>
      <category>serverless</category>
      <category>webdev</category>
    </item>
    <item>
      <title>AWS RDS &amp; ElastiCache remote access with Port7777</title>
      <dc:creator>Robert Slootjes</dc:creator>
      <pubDate>Fri, 10 Nov 2023 13:18:04 +0000</pubDate>
      <link>https://dev.to/slootjes/aws-rds-elasticache-remote-access-with-port7777-2hpn</link>
      <guid>https://dev.to/slootjes/aws-rds-elasticache-remote-access-with-port7777-2hpn</guid>
      <description>&lt;p&gt;Sometimes you just need to be able to quickly access a database that's inside a VPC. I stumbled upon a tool called &lt;a href="https://port7777.com/" rel="noopener noreferrer"&gt;Port7777&lt;/a&gt; and it promises that it can do exactly that for both RDS and ElastiCache.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt; This is &lt;em&gt;not&lt;/em&gt; a sponsored post. My employer paid for a team license earlier this year and I am genuinely happy with it and I think it can greatly benefit others too.&lt;/p&gt;

&lt;h1&gt;
  
  
  Port7777
&lt;/h1&gt;

&lt;p&gt;So, what is it and how does it work?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;7777 automates the work of creating and using a jump server to reach private databases in AWS VPC.&lt;/p&gt;

&lt;p&gt;By internally using AWS Fargate containers instead of EC2 instances, the setup time and costs are drastically reduced. 7777 creates jump servers using containers on the fly, and deletes them after you are finished.&lt;/p&gt;

&lt;p&gt;Finally, everything runs in your AWS account, including the Fargate containers. Nothing is sent to 3rd party servers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Simply said, the tool uses a Fargate container to setup an SSH tunnel between the remote database and your device and cleans it up automatically afterwards so you don't have a server running 24/7.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pricing
&lt;/h1&gt;

&lt;p&gt;This tool is cheap, very cheap, maybe a bit too cheap for what it solves if you ask me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$19 for a solo license, 1 user with unlimited databases, unlimited tunnels&lt;/li&gt;
&lt;li&gt;$99 for a team license, unlimited users allowing it to be used in your team &lt;em&gt;and&lt;/em&gt; &lt;a href="https://github.com/whilenull/7777-support/blob/main/CI-CD.md" rel="noopener noreferrer"&gt;CI/CD pipelines&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that both licenses are a &lt;strong&gt;one-time purchase&lt;/strong&gt; (!).&lt;/p&gt;

&lt;p&gt;Obviously running the Fargate container itself costs &lt;a href="https://aws.amazon.com/fargate/pricing/" rel="noopener noreferrer"&gt;some money&lt;/a&gt; but this costs only a few dollar per month per tunnel assuming you don't need it all the time.&lt;/p&gt;

&lt;h1&gt;
  
  
  Security
&lt;/h1&gt;

&lt;p&gt;While this tool is developed by 2 very trustworthy people I didn't like the aspect of this tool grabbing my AWS credentials and doing something with it in a black box. I gave myself the task to come up with a solution where we are in control of the credentials the tool could access.&lt;/p&gt;

&lt;h1&gt;
  
  
  Isolation with Docker
&lt;/h1&gt;

&lt;p&gt;To increase security I looked into the offered option of running 7777 in Docker and manually setting up the stack. This way I can be sure that the tool runs completely isolated and only has access to the AWS credentials that I specifically specified with the correct policy. While there was some &lt;a href="https://github.com/whilenull/7777-support/blob/main/AWS-manual-installation.md" rel="noopener noreferrer"&gt;documentation on manual installation&lt;/a&gt; available it took me a while to figure it out.&lt;/p&gt;

&lt;p&gt;Basically the steps are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install Port7777 stack using CloudFormation&lt;/li&gt;
&lt;li&gt;Create a policy + user with minimal permissions&lt;/li&gt;
&lt;li&gt;Store credentials for this user in an env file&lt;/li&gt;
&lt;li&gt;Start Port7777 inside a Docker container passing the env file containing the AWS permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Sharing is caring
&lt;/h1&gt;

&lt;p&gt;I created a repository which contains the templates to set up this tool manually in your AWS account and a shell script that makes it similar in use to the original tool. I shared it previously with the other developers within my company but I thought it might also be helpful to others so I put it on my GitHub account for everyone to use: &lt;a href="https://github.com/slootjes/port-7777-isolated" rel="noopener noreferrer"&gt;https://github.com/slootjes/port-7777-isolated&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Enterprise
&lt;/h1&gt;

&lt;p&gt;Recently they also added an enterprise license which is a subscription-based plan where they allow a customer to fork the project in a private GitHub repository. This allows you to analyse the code, run security scanners and self-build the tool. I don't know about pricing details on this subscription so you will have to &lt;a href="mailto:contact@port7777.com"&gt;contact&lt;/a&gt; them about it yourself if you're interested.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>rds</category>
      <category>redis</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Optimizing Typescript packages in Serverless Framework with esbuild</title>
      <dc:creator>Robert Slootjes</dc:creator>
      <pubDate>Fri, 12 May 2023 12:44:54 +0000</pubDate>
      <link>https://dev.to/slootjes/optimizing-typescript-packages-in-serverless-framework-with-esbuild-1ol4</link>
      <guid>https://dev.to/slootjes/optimizing-typescript-packages-in-serverless-framework-with-esbuild-1ol4</guid>
      <description>&lt;p&gt;&lt;strong&gt;Update April 14, 2025&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;/u&lt;/code&gt; modifier is breaking for the latest Go-based typescript compiler as the modifier is not valid in Go. Luckily just removing the modifier is enough to make it work again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update December 16, 2024&lt;/strong&gt;&lt;br&gt;
This article applies to Serverless Framework &lt;em&gt;v3&lt;/em&gt; which is no longer maintained. You can seamlessly switch to the &lt;a href="https://github.com/oss-serverless/serverless" rel="noopener noreferrer"&gt;open source fork&lt;/a&gt; which is a drop-in replacement.&lt;/p&gt;



&lt;p&gt;I'm a big fan of &lt;a href="https://www.serverless.com/framework" rel="noopener noreferrer"&gt;Serverless Framework&lt;/a&gt; as it takes care of a lot of things behind the scenes and offers many plugins. I do my work in &lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;Typescript&lt;/a&gt; and I was using &lt;a href="https://www.serverless.com/plugins/serverless-plugin-typescript" rel="noopener noreferrer"&gt;serverless-plugin-typescript&lt;/a&gt; that takes care of transpiling into Javascript. Next to that, I was using a combination of &lt;a href="https://www.serverless.com/plugins/serverless-plugin-common-excludes" rel="noopener noreferrer"&gt;serverless-plugin-common-excludes&lt;/a&gt; and a &lt;strong&gt;huge&lt;/strong&gt; custom exclude list to reduce the size of the package (zip files) being uploaded to Lambda.&lt;/p&gt;

&lt;p&gt;Doing a deploy would take around 2 minutes (on a Intel Core i5-13600K processor with 32GB RAM for who is interested) and in my project resulted in a 4MB package. While 4MB isn't a lot, it still contained a lot of code which wasn't actually used. The cold start time of the Lambda was around 4 seconds which bothered me a lot. While this seems to be caused by an &lt;a href="https://github.com/aws/aws-lambda-base-images/issues/83" rel="noopener noreferrer"&gt;unresolved issue&lt;/a&gt; at AWS with the node 18 runtime, I felt like I shouldn't sit around and wait for a solution.&lt;/p&gt;

&lt;p&gt;When looking for options to reduce the package size I stumbled upon the &lt;a href="https://www.serverless.com/plugins/serverless-esbuild" rel="noopener noreferrer"&gt;serverless-esbuild&lt;/a&gt; plugin which uses &lt;a href="https://esbuild.github.io/" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt; which is supposed to be super fast &lt;em&gt;(spoiler alert: it is!)&lt;/em&gt;. While the plugin works without configuration it felt like it was capable of more.&lt;/p&gt;
&lt;h1&gt;
  
  
  Goals
&lt;/h1&gt;

&lt;p&gt;I hoped to achieve the following things:&lt;/p&gt;
&lt;h2&gt;
  
  
  Fast builds
&lt;/h2&gt;

&lt;p&gt;While local invocation and tools like LocalStack are awesome, I prefer to just upload to AWS and run everything on the actual (dev) environment. Waiting for a deploy of several minutes can be really annoying so the faster it builds, the faster I can work.&lt;/p&gt;
&lt;h2&gt;
  
  
  Small package size
&lt;/h2&gt;

&lt;p&gt;Having a small package size has multiple advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster deployments&lt;/li&gt;
&lt;li&gt;Lower cold start time&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Proper stack traces
&lt;/h2&gt;

&lt;p&gt;When something goes wrong unexpectedly and you go to CloudWatch it's very painful to only see unreadable stack traces because of bundled, minified code. This is where &lt;a href="https://web.dev/source-maps/" rel="noopener noreferrer"&gt;source maps&lt;/a&gt; come in. Source maps can be used to transform bundled code back into their original file locations and line numbers.&lt;/p&gt;

&lt;p&gt;The problem I faced with source maps is that once generated, they were huge (1 MB code, 8 MB source map) as it included &lt;em&gt;all&lt;/em&gt; the data from all external packages while usually the errors are within my own code. In case something went wrong, it would take &lt;em&gt;around 4 seconds&lt;/em&gt; to load and process the source map to generate the stack trace, that was unacceptable to me as Lambda is paid for by the millisecond. The slow processing of source maps seems to be an &lt;a href="https://github.com/nodejs/node/issues/41541" rel="noopener noreferrer"&gt;issue&lt;/a&gt; from nodejs itself.&lt;/p&gt;
&lt;h1&gt;
  
  
  Result
&lt;/h1&gt;

&lt;p&gt;After fiddling around with configurations and reading many issues on Github I eventually ended up with the following configuration that seems to tick off the boxes for me:&lt;/p&gt;
&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;serverless.yaml&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugins:
  - serverless-esbuild # enables esbuild plugin

package:
  individually: true # an optimized package per function

custom:
  esbuild:
    config: './esbuild.config.cjs' # external config file

environment:
  NODE_OPTIONS: '--enable-source-maps' # use source map if available
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;esbuild.config.cjs&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const {Buffer} = require('node:buffer');
const fs = require('node:fs');
const path = require('node:path');

// inspired by https://github.com/evanw/esbuild/issues/1685
const excludeVendorFromSourceMap = (includes = []) =&amp;gt; ({
    name: 'excludeVendorFromSourceMap',
    setup(build) {
        const emptySourceMap = '\n//# sourceMappingURL=data:application/json;base64,' + Buffer.from(JSON.stringify({
            version: 3,
            sources: [''],
            mappings: 'A',
        })).toString('base64');

        build.onLoad({filter: /node_modules/}, async (args) =&amp;gt; {
            if (/\.[mc]?js$/.test(args.path)
                &amp;amp;&amp;amp; !new RegExp(includes.join('|'), 'u').test(args.path.split(path.sep).join(path.posix.sep))
            ) {
                return {
                    contents: `${await fs.promises.readFile(args.path, 'utf8')}${emptySourceMap}`,
                    loader: 'default',
                };
            }
        });
    },
});

module.exports = () =&amp;gt; {
    return {
        format: 'esm',
        minify: true,
        sourcemap: true,
        sourcesContent: false,
        keepNames: false,
        outputFileExtension: '.mjs',
        plugins: [excludeVendorFromSourceMap(['@my-vendor', 'other/package'])],
        banner: {
            // https://github.com/evanw/esbuild/issues/1921
            js: "import { createRequire } from 'module';const require = createRequire(import.meta.url);",
        }
    };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Targets the node version as configured in Serverless Framework (in my case: node 18)&lt;/li&gt;
&lt;li&gt;Package every function into a separate, optimized single file using tree shaking and minification&lt;/li&gt;
&lt;li&gt;Using ESM (instead of CJS) allowing things like top level await which can be very useful&lt;/li&gt;
&lt;li&gt;Create empty source maps for external packages &lt;em&gt;except&lt;/em&gt; for manually enabled ones to prevent having a giant source map&lt;/li&gt;
&lt;li&gt;Includes a "banner" to support packages that don't support ESM yet.&lt;/li&gt;
&lt;li&gt;Works on Windows and *nix machines&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Good
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Extremely fast build process - it only takes miliseconds to transpile Typescript and package&lt;/li&gt;
&lt;li&gt;Really small package size&lt;/li&gt;
&lt;li&gt;Cold start went from around 4 seconds to 1.5 second, a massive improvement!&lt;/li&gt;
&lt;li&gt;Readable stack traces for my own source code and manually enabled vendor code&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Less Good
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Even with a smaller source map it still takes node quite some time to process an error - this delay is around 100ms - 400ms for my use cases. Not super fast but way more acceptable than the 4 to 5 seconds without optimization obviously.&lt;/li&gt;
&lt;li&gt;If something unexpected now happens within vendor code, it won't show up in the stack trace unless it was configured to be included in the source maps.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Verdict
&lt;/h1&gt;

&lt;p&gt;While I was a bit skeptical at first to switch to esbuild, I'm quite happy with the end result. I hope this helps someone else to find a good configuration for their use case. If you have some tips or tricks that I've missed, please share them in the comments.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>typescript</category>
      <category>esbuild</category>
    </item>
    <item>
      <title>QLDB to EventBridge using Pipes</title>
      <dc:creator>Robert Slootjes</dc:creator>
      <pubDate>Sat, 04 Mar 2023 12:26:56 +0000</pubDate>
      <link>https://dev.to/slootjes/qldb-to-eventbridge-using-pipes-1lgf</link>
      <guid>https://dev.to/slootjes/qldb-to-eventbridge-using-pipes-1lgf</guid>
      <description>&lt;p&gt;Last year I did a project with AWS Quantum Ledger Database or simply &lt;em&gt;QLDB&lt;/em&gt;. QLDB is a fully managed ledger database which keeps track of every change through a journal. This makes it possible to see the complete history inserts, updates and deletes but also queries being fired at it. As a database it has its limitations but this is a very nice product if having a reliable audit trail is crucial.&lt;/p&gt;

&lt;p&gt;For this project I was looking for a reliable way to act on changes in the database using EventBridge without the application publishing it. I prefer to do everything async as much as possible to avoid the database record to have changed and have the application fail to publish the event as that means I lost valuable information. The good news is that QLBD supports &lt;a href="https://docs.aws.amazon.com/qldb/latest/developerguide/streams.html" rel="noopener noreferrer"&gt;Streams&lt;/a&gt; which sends it data to a &lt;a href="https://aws.amazon.com/kinesis/data-streams/" rel="noopener noreferrer"&gt;Kinesis Data Stream&lt;/a&gt;. This way, you do not have to worry about losing any data. It sounds pretty easy and it kind of is but the bad news is that the records from QLDB are in the &lt;a href="https://docs.aws.amazon.com/qldb/latest/developerguide/ion.html" rel="noopener noreferrer"&gt;ion&lt;/a&gt; format and on top of that, Kinesis also &lt;a href="https://docs.aws.amazon.com/streams/latest/dev/kinesis-kpl-concepts.html#kinesis-kpl-concepts-aggretation" rel="noopener noreferrer"&gt;aggregates&lt;/a&gt; the records.&lt;/p&gt;

&lt;p&gt;An event from QLDB in a Kinesis Data Stream looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  {
    "eventSource": "aws:kinesis",
    "eventVersion": "1.0",
    "eventID": "shardId-000000000000:49636282114612585653977882531504014001922575972444405762",
    "eventName": "aws:kinesis:record",
    "invokeIdentityArn": "arn:aws:iam::123456:role/qldbpipe-ledger-stream-pipe-role",
    "awsRegion": "eu-west-1",
    "eventSourceARN": "arn:aws:kinesis:eu-west-1:123456:stream/qldbpipe-dev-ledger",
    "kinesisSchemaVersion": "1.0",
    "partitionKey": "aa2f52e2b20be04ac9e38683dff18447fecf9e89a62bf5744428e6b650138f37",
    "sequenceNumber": "49636282114612585653977882531504014001922575972444405762",
    "data": "4RVR6l4S5ZXU3xYwy74S24ZosXIzL3IpQNWkHOAlzeAcP29pQWI5tXNYtXc5sX9yQZozsX9arFWbQYAct3FZt3IpPN5bJNJBt2MouNMlP2MFs4ZFtdWlt2WauXcmsbcbaf5zsX9arZIgsNMquXWktZczsX9arFyyt2zCQN5FtdcctFyyt2zFbOSpQOQgs3MqHdomP2kZPOEfaf9cseIprNMqJXWqrVogt3JFa3IpPN5qPNEFrN9lJN5ds4gquXWFyNZcseIqzOEFPOIcsNMluZcquXWpuWIgsNNFa3EFPOIcsNM9uVIgQ2MquE4UnPhFqdWpsagyu3D68NobPagcuJZ3QOEFCKV6FUbpDab3FKLpDUtoFeEFtdMysJ9osXIztXcnQJZbQOPmDYuQKZtnDKMmNOPqEWgkIMckJbgXDPlEHboGHFkwLZM3LWJNPqvRk2E3g2FagQWJ3uVuOW6rZQKJKAArOMWPaSLsYRnapZTW51FcbgZud9nrdHpJL9ADZQZEWEZrcANQqrIrfRG5fpLa5FxnpTJihThCZCzjxmxJjeayfGw8PIY/j+vzrPi9OIVBFr2LSFGE5FlfWdZUhLOZX34TgjOBGTyb7vGCYOOPeF/3Fck2ehCuuUucB6xd7uSYSN/v92EwhOF4pQZZNDOKmy3ZIlFEjlGLUpSdK2MmjNlfEdbg//sUl838Ny84FwbqcrgmIEolPaJ69vF7n+MiJ6ZfB6xuU2SGVGaxanFqZBQGHLRs9ZSxIVH7Ymd2ZzsEHqbwllN3lJOmlYv35zFiWEWKVMUMTRhZVQJKFFxrN5ds3AkPOIgs25wt2EfQNZyCeMqQOAwuXWzsX7qdNlRU+rDcZ+Ke8DUn5hlfC67Zfb4/laSZmVAdyY5sSZY+BuFxQFoFIBRh4f4Jmh3",
    "approximateArrivalTimestamp": 1671549572.187
  }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, now what? If this was published to EventBridge, adding filter rules to this data isn't really possible...&lt;/p&gt;

&lt;p&gt;What I did a year ago is that I had a Lambda function as a trigger on the Kinesis Data Stream that &lt;em&gt;deaggregates&lt;/em&gt; the records and converts &lt;em&gt;ion&lt;/em&gt; into &lt;em&gt;json&lt;/em&gt;. Then I would publish it to EventBridge. This worked perfectly fine but when &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-pipes.html" rel="noopener noreferrer"&gt;EventBridge Pipes&lt;/a&gt; launched I wanted to see if I could reduce the amount of code I needed to achieve the same result.&lt;/p&gt;

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

&lt;p&gt;In this scenario a &lt;strong&gt;Kinesis Data Stream is the source&lt;/strong&gt; and the &lt;strong&gt;EventBridge is the target&lt;/strong&gt;. The only glue required is a Lambda that takes care of the &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-pipes.html#pipes-enrichment" rel="noopener noreferrer"&gt;enrichment&lt;/a&gt; which in my case is &lt;em&gt;deaggregating&lt;/em&gt; and converting from &lt;em&gt;ion to json&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;As I strongly believe you shouldn't be clicking things in the console but rather use CloudFormation I created a fully working proof of concept using Serverless Framework which &lt;a href="https://github.com/slootjes/aws-qldb-to-eventbridge" rel="noopener noreferrer"&gt;you can find on my Github&lt;/a&gt;. In this proof of concept I'm just the &lt;a href="https://dev.to/slootjes/logging-eventbridge-events-to-s3-with-firehose-2hkc"&gt;logging the events to S3&lt;/a&gt; but you can tweak this to suit your needs of course. Also, if anyone has a better way of converting ion to json, please share it as I went for quick and dirty (it is very effective however :-).&lt;/p&gt;

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

&lt;p&gt;With the proof of concept a SELECT query done on QLDB would look something like this when published to EventBridge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "version": "0",
  "id": "4dad5941-44b2-d3cc-4373-5763ebec04cb",
  "detail-type": "Event from aws:kinesis",
  "source": "Pipe qldbpipe-dev-ledger",
  "account": "123456",
  "time": "2023-03-04T09:41:35Z",
  "region": "eu-west-1",
  "resources": [],
  "detail": {
    "qldbStreamArn": "arn:aws:qldb:eu-west-1:123456:stream/qldbpipe-dev/0wYOW015oYv34ZmEYmJJF2",
    "recordType": "BLOCK_SUMMARY",
    "payload": {
      "blockAddress": {
        "strandId": "EKwDuqzkVSI2IiuEb0Tlp1",
        "sequenceNo": 653
      },
      "transactionId": "EFcCQMkOEPXDj5aVyogJbj",
      "blockTimestamp": "2023-03-04T09:41:35.292Z",
      "blockHash": "Z6gKNWpJ+kmeHLVmr2ZjT05ca4XhdbL6YU6Y5phGcEY=",
      "entriesHash": "AhDroVPQiFYltthOkVoGlwONAmG+tIBrvlzHVColmO7=",
      "previousBlockHash": "rGsA9+leZON3iZGRqW8PB2CaWVc0Lminy1DOSrbpKZ7=",
      "entriesHashList": ["AhCD3Slvf3F+wCazKP12zK0upXJ44/weIsfd7ceaFvI=", "", "a60i/33Xewkp2koI7+06NkuAt8v5qRGpKjCBVqdfe17="],
      "transactionInfo": {
        "statements": [{
          "statement": "SELECT * FROM tickets",
          "startTime": "2023-03-04T09:41:35.259Z",
          "statementDigest": "ux//sy9PGxtDcFw6VzZkzxssE74KCOA3vCULzGs/vCc="
        }]
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course this is still very verbose but at least I can add filter rules for this in EventBridge. Optionally you could also adjust the Lambda code to &lt;em&gt;filter&lt;/em&gt; records or remove information you don't need. For instance, you might be interested only in inserts and updates.&lt;/p&gt;

&lt;p&gt;Overall I'm quite happy with the end result as the only code I need to maintain now is a very simple transformer. What bothers me though is that it currently doesn't seem possible is to override the &lt;em&gt;source&lt;/em&gt; and &lt;em&gt;detail-type&lt;/em&gt;, these will be set by the Pipe itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"detail-type": "Event from aws:kinesis",
"source": "Pipe qldbpipe-dev-ledger",
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I think it would be a very nice feature to for instance set the source to "qldb" and the detail type to something like "select" or "insert". We could change the target of the pipe to be an SQS queue where we attach a Lambda that manually publishes the records to EventBridge with this configuration but that would add more glue code which I tried to avoid with this proof of concept. At this moment I am not yet sure what I will be using in production in my next project but for sure this gave me some good insights in the possibilities of EventBridge Pipes. Let me know what you would prefer in the comments.&lt;/p&gt;




&lt;p&gt;To close off I'm excited and proud to announce that I was recently selected as Community Builder by AWS. Follow me to not miss any upcoming articles.&lt;/p&gt;

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

</description>
      <category>learning</category>
      <category>motivation</category>
      <category>discuss</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>API Gateway Websockets vs IoT Core</title>
      <dc:creator>Robert Slootjes</dc:creator>
      <pubDate>Wed, 23 Nov 2022 16:00:37 +0000</pubDate>
      <link>https://dev.to/slootjes/api-gateway-websockets-vs-iot-core-1me5</link>
      <guid>https://dev.to/slootjes/api-gateway-websockets-vs-iot-core-1me5</guid>
      <description>&lt;p&gt;When you need websockets in a project on AWS most likely &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-overview.html" rel="noopener noreferrer"&gt;API Gateway Websockets&lt;/a&gt; (I will refer to it as API Gateway from now on) is the first service coming to mind. At some point when looking into options, I ran into &lt;a href="https://aws.amazon.com/iot-core/" rel="noopener noreferrer"&gt;IoT Core&lt;/a&gt; instead. I thought this was meant only for very specific scenarios involving hardware; however it also supports &lt;a href="https://mqtt.org/" rel="noopener noreferrer"&gt;MQTT&lt;/a&gt; over websockets which makes it an amazing choice for web and app. I think this is a hidden gem in the AWS ecosystem and in this post I will explain why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connection Management
&lt;/h2&gt;

&lt;h3&gt;
  
  
  API Gateway
&lt;/h3&gt;

&lt;p&gt;With API Gateway you need to &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-route-keys-connect-disconnect.html" rel="noopener noreferrer"&gt;manage your own connections&lt;/a&gt;. This means you need to listen to $connect and $disconnect events and store connection IDs in a database (DynamoDB or Redis would be very good choices). As they do not guarantee a $disconnect for every connection you are responsible for your own garbage collection on top of this.&lt;/p&gt;

&lt;h3&gt;
  
  
  IoT Core
&lt;/h3&gt;

&lt;p&gt;IoT Core manages everything for you! &lt;em&gt;Optionally&lt;/em&gt; you can still listen for connect and disconnect events using &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/life-cycle-events.html" rel="noopener noreferrer"&gt;Lifecycle events&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Messaging
&lt;/h2&gt;

&lt;h3&gt;
  
  
  API Gateway
&lt;/h3&gt;

&lt;p&gt;Sending a message is done through the API but allows to &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-how-to-call-websocket-api-connections.html" rel="noopener noreferrer"&gt;send to 1 connection at a time&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to send a message to all connected users, you will need to loop over all connections in your database and send them a message individually. You could offload this work to an SQS queue + Lambda function but it's not ideal.&lt;/p&gt;

&lt;p&gt;A noteable feature is &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-data-transformations.html" rel="noopener noreferrer"&gt;data transformation&lt;/a&gt; which allows to transform inputs and outputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  IoT Core
&lt;/h3&gt;

&lt;p&gt;Because IoT Core is an MQTT broker, your clients can subscribe (including wildcards!) and publish to &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/topics.html" rel="noopener noreferrer"&gt;topics&lt;/a&gt;. Sending a message to all connected clients from the server is as easy as letting your clients subscribe to the same topic and then doing a single &lt;a href="https://docs.aws.amazon.com/iot/latest/apireference/API_iotdata_Publish.html" rel="noopener noreferrer"&gt;publish API request&lt;/a&gt; to the topic to send the message to everyone at once. You can also create a topic for an individual user by their user ID (eg: &lt;code&gt;user/2d543007-2951-43cc-bec4-96f6649b4f53&lt;/code&gt;) and send individual messages if needed of course. Keep in mind however that &lt;em&gt;depending on how you set up access control&lt;/em&gt;, others could potentially read along.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  API Gateway
&lt;/h3&gt;

&lt;p&gt;Using &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-develop-routes.html" rel="noopener noreferrer"&gt;custom routes&lt;/a&gt; you can create your own integrations based on the body of the received message. Any non-matched messages can be routed to a default route. It is possible to trigger a Lambda, call HTTP endpoints but also there is a possibility to call other AWS Services (especially useful in combination with data transformation).&lt;/p&gt;

&lt;h3&gt;
  
  
  IoT Core
&lt;/h3&gt;

&lt;p&gt;Handing received messages can be done with &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-rules.html" rel="noopener noreferrer"&gt;Rules&lt;/a&gt; which uses an SQL-like syntax. &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-rule-actions.html" rel="noopener noreferrer"&gt;Rule Actions&lt;/a&gt; specify what to do when a rule is hit and there are tons of out of the box integrations available. These integrations include Lambda, SQS, SNS &amp;amp; Kinesis to name a few.&lt;/p&gt;

&lt;p&gt;Optionally you can &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-basic-ingest.html" rel="noopener noreferrer"&gt;send messages to a rule directly&lt;/a&gt; to save on messaging costs!&lt;/p&gt;

&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;h3&gt;
  
  
  API Gateway
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-control-access.html" rel="noopener noreferrer"&gt;Controlling access&lt;/a&gt; for websockets is similar to REST APIs. The option to use a Lambda authorizers and IAM authorization is very flexible.&lt;/p&gt;

&lt;p&gt;Other useful security features include &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-request-validation.html" rel="noopener noreferrer"&gt;request validation&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-protect.html" rel="noopener noreferrer"&gt;account level and route-level throttling&lt;/a&gt;. Using a custom authorizer you can also implement your own throttling mechanism.&lt;/p&gt;

&lt;h3&gt;
  
  
  IoT Core
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/security.html" rel="noopener noreferrer"&gt;Security in IoT Core&lt;/a&gt; has much more options than API Gateway since it has many different use cases. Luckily &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/custom-auth.html#custom-auth-websockets" rel="noopener noreferrer"&gt;custom authentication&lt;/a&gt; allows for a lot of flexibility. This option doesn't only make it possible to allow or deny access to IoT Core but also specify exactly what is allowed to be done (ie: read only on specific topics). I will create a separate post about subject this soon.&lt;/p&gt;

&lt;p&gt;There is also another option that doesn't seem to be documented; a pre-signed url! I found &lt;a href="https://github.com/gojko/aws-device-gateway-signed-url" rel="noopener noreferrer"&gt;a library&lt;/a&gt; that does the trick perfectly. To control what the user can do you must create a IAM user for it with the right permissions on &lt;code&gt;iot:Connect&lt;/code&gt;, &lt;code&gt;iot:Subscribe&lt;/code&gt;, &lt;code&gt;iot:Receive&lt;/code&gt; and &lt;code&gt;iot:Publish&lt;/code&gt; and use that to sign the request. Your client can then simply connect to the pre-signed url and do it's thing. The pre-signed url is reusable for multiple users which means you can cache it on your CDN. Keep in mind this does give every user the same permissions but in some cases this is exactly what you need.&lt;/p&gt;

&lt;p&gt;In all cases, whenever the client tries to do something it isn't allowed (ie: publish to a topic which the IAM user doesn't have permission to) the server will disconnect the client.&lt;/p&gt;

&lt;p&gt;IoT Core does not seem to have any request validation or throttling features as of this moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;Pricing obviously is a bit different per region but overall the differences are similar. For this post I've chosen eu-west-1 since that is my region of choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  API Gateway
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;1 Million connection minutes: &lt;strong&gt;$0.285&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Messages measured per: &lt;strong&gt;32 KB&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;First 1 billion: &lt;strong&gt;$1.14&lt;/strong&gt; per million&lt;/li&gt;
&lt;li&gt;Over 1 billion: &lt;strong&gt;$0.94&lt;/strong&gt; per million&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/api-gateway/pricing/" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  IoT Core
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;1 Million connection minutes: &lt;strong&gt;$0.08&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Messages measured per: &lt;strong&gt;5 KB&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Up to 1 billion messages: &lt;strong&gt;$1.00&lt;/strong&gt; per million&lt;/li&gt;
&lt;li&gt;Next 4 billion messages: &lt;strong&gt;$0.80&lt;/strong&gt; per million&lt;/li&gt;
&lt;li&gt;Over 5 billion messages: &lt;strong&gt;$0.70&lt;/strong&gt; per million&lt;/li&gt;
&lt;li&gt;Rules triggered: &lt;strong&gt;$0.15&lt;/strong&gt; per million&lt;/li&gt;
&lt;li&gt;Rule Actions executed: &lt;strong&gt;$0.15&lt;/strong&gt; per million&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Messages sent or received using the reserved topics of &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-basic-ingest.html" rel="noopener noreferrer"&gt;Basic Ingest&lt;/a&gt; or &lt;a href="https://aws.amazon.com/iot-core/features/#Alexa_Voice_Service_.28AVS.29_Integration" rel="noopener noreferrer"&gt;Alexa Voice Service Integration for AWS IoT Core&lt;/a&gt; are &lt;strong&gt;free&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/iot-core/pricing/" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Differences&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IoT Core connection minutes are &lt;em&gt;~3 times cheaper&lt;/em&gt; than API Gateway&lt;/li&gt;
&lt;li&gt;Message pricing looks similar but API Gateway meters messages per &lt;em&gt;32 KB&lt;/em&gt; while IoT Core meters messages per &lt;em&gt;5 KB&lt;/em&gt;, a ~6.5 times difference&lt;/li&gt;
&lt;li&gt;Receiving and sending messages in IoT Core can be free in &lt;em&gt;some cases&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;IoT Core charges a fee for Rules and Rule actions, in API Gateway you will be charged for Lambda execution time instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Depending on how many messages, the size of your messages and your use case you can calculate easily which one would be cheaper to use. Don't forget though that with API Gateway you do need a database to manage your connections which also costs you extra. Also for sending messages in API Gateway you will most likely need a queue and a Lambda which adds more cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scalability
&lt;/h2&gt;

&lt;p&gt;I've selected some of the most important limits for both services.&lt;/p&gt;

&lt;h3&gt;
  
  
  API Gateway
&lt;/h3&gt;

&lt;p&gt;New connections per second per account per region: 500&lt;br&gt;
Concurrent connections: no enforced limit (!)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html#apigateway-execution-service-websocket-limits-table" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;500 new connections per second is a lot and can be increased if needed. Having no limit is very cool but managing these connections in your own database might also add some challenges.&lt;/p&gt;

&lt;h3&gt;
  
  
  IoT Core
&lt;/h3&gt;

&lt;p&gt;New connections per second per account: 500&lt;br&gt;
Subscriptions per second per account: 500&lt;br&gt;
Inbound publish requests per second per account: 20.000&lt;br&gt;
Outbound publish requests per second per account: 20.000&lt;br&gt;
Maximum concurrent client connections per account: 500.000&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/general/latest/gr/iot-core.html#rules-limits" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This seems to be a bit strict compared to API Gateway, luckily all these limits can be increased. I've had to do this a few times and I was able to set both inbound and outbound publish requests per second to 50.000 but it took some effort with the AWS support team to get it done (and it's understandable why).&lt;/p&gt;

&lt;p&gt;I couldn't really find what happens if you exceed these publish limits. Does the message get sent to a limited amount of users, does no one receive it, will it get spread or queued automatically? If anyone has more information on this, please let me know about it as I would love to know more about it.&lt;/p&gt;

&lt;p&gt;A possible solution could be to split users in multiple groups round robin and send a message to every group with some delays. On the client side, when sending messages, adding a random delay and doing a exponential back off retry mechanism can be very effective.&lt;/p&gt;

&lt;p&gt;For most scenarios the default limits will probably be more than enough and you don't need to worry about these things at all.&lt;/p&gt;

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

&lt;p&gt;Both are great services with their ups and downs. The main reasons why I prefer IoT Core over API Gateway:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No connection management&lt;/li&gt;
&lt;li&gt;Working with topics instead of individual connections&lt;/li&gt;
&lt;li&gt;Much cheaper if you can keep the size under 5 KB per message&lt;/li&gt;
&lt;li&gt;Less code required&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Thanks
&lt;/h2&gt;

&lt;p&gt;Thanks to my colleague Iran Reyes who provided some good insights on the usage of API Gateway Websockets.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>mqtt</category>
      <category>websockets</category>
    </item>
  </channel>
</rss>
