<?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: Serverless By Theodo</title>
    <description>The latest articles on DEV Community by Serverless By Theodo (@slsbytheodo).</description>
    <link>https://dev.to/slsbytheodo</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%2Forganization%2Fprofile_image%2F4917%2Ff661de8e-618a-4c66-bb31-380771b2fbf5.png</url>
      <title>DEV Community: Serverless By Theodo</title>
      <link>https://dev.to/slsbytheodo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/slsbytheodo"/>
    <language>en</language>
    <item>
      <title>Avoiding API Gateway’s integrations hard limit: scaling serverless architectures efficiently</title>
      <dc:creator>Anaïs Schlienger</dc:creator>
      <pubDate>Tue, 26 Nov 2024 07:00:00 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/avoiding-api-gateways-integrations-hard-limit-scaling-serverless-architectures-efficiently-49on</link>
      <guid>https://dev.to/slsbytheodo/avoiding-api-gateways-integrations-hard-limit-scaling-serverless-architectures-efficiently-49on</guid>
      <description>&lt;p&gt;Avoid AWS API Gateway’s integration limit with efficient multi-API Gateway setups. Learn how to scale serverless projects!&lt;/p&gt;

&lt;p&gt;Last week, I was coding a new feature. It was looking good, until I had to deploy. AWS cli yelled at me and refused to deploy my stack. I got the following error : &lt;code&gt;CREATE_FAILED: HttpApiIntegration (AWS::ApiGatewayV2::Integration) Maximum number of Integrations for this API has been reached.&lt;/code&gt; Did this ever happen to you? If it did not, and you are using AWS, then it might be worth to run a quick sanity check on your architecture.&lt;/p&gt;

&lt;p&gt;AWS combined with API Gateway is widely used for scale-up apps. If API Gateway can handle a lot of routes (there is a 300 quota that can be increased), it has however a lesser-known but critical &lt;a href="https://www.alexdebrie.com/posts/api-gateway-elements/#api-gateway-integration-passthrough-behavior" rel="noopener noreferrer"&gt;limitation of &lt;strong&gt;300 integrations per API Gateway instance&lt;/strong&gt;.&lt;/a&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An API Gateway integration is a connection between an API Gateway and a backend service, such as a Lambda function, an HTTP endpoint, or an AWS service, allowing the API Gateway to route incoming requests to the appropriate service for processing and return responses to the client. An endpoint can have only one integration. Here is a &lt;a href="https://www.alexdebrie.com/posts/api-gateway-elements/#api-gateway-integration-passthrough-behavior" rel="noopener noreferrer"&gt;great article about integrations.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a non-monolithic serverless architecture, where every Lambda function represents a different endpoint, it’s easy to hit this limit once your app grows. This was exactly what happened to me in a large-scale serverless project using the Serverless Framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The challenge: reaching API Gateway’s 300 integration limit&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Let’s represent my project as an online library, where users can read, buy, lend and comment on books. The backend is split into two services, &lt;code&gt;users&lt;/code&gt; and &lt;code&gt;books&lt;/code&gt;, representing the core features.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F57fb3tq285y0yebwilpm.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%2F57fb3tq285y0yebwilpm.png" alt="Initial architecture" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our architecture, typical of many non-monolithic setups, follows a one Lambda per endpoint model. When you have multiple services each exposing dozens of endpoints, it doesn’t take long to bump against the 300 integration per API limit (in this simplified example with only 2 services, it might take a long time I agree). This bottleneck emerged (I was not aware of it before!) when our API maxed out its available integrations and simply refused to deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Evaluating the options: lambda-lith vs micro-APIs&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;With an architecture with all services living behind the same API Gateway, we faced two clear paths forward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Split the API into smaller chunks (micro-APIs).&lt;/li&gt;
&lt;li&gt;Shift to a more monolithic Lambda setup, where multiple endpoints share a Lambda.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxxgxshkgsjkjnoelkqwy.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%2Fxxgxshkgsjkjnoelkqwy.png" alt="Option 1" width="646" height="754"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftd6cr0g8e7gtoqg7r3b1.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%2Ftd6cr0g8e7gtoqg7r3b1.png" alt="Option 2" width="800" height="645"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Going down the monolith route would have required significant changes to our architecture, a large refactor, and a fair amount of coordination with the front-end. This wasn't ideal for us since we wanted minimal disruption to our existing setup and quick implementation. So we opted for the second option: splitting the API into smaller chunks by creating a new API Gateway for each service.&lt;/p&gt;

&lt;p&gt;This approach allowed us to break our system into manageable micro-services while keeping our frontend mostly unchanged.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The solution: multi-API Gateways behind CloudFront&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The architecture change was made easier by the fact that we had CloudFront in front of our API Gateway. Our frontend communicates with CloudFront, and CloudFront handles routing requests to our API. &lt;/p&gt;

&lt;p&gt;This setup meant that adding new API Gateways could be done without any drastic changes to the frontend. All we needed to do was redirect calls to the appropriate API Gateway, based on the service prefix.&lt;/p&gt;

&lt;p&gt;Here’s how we did it step-by-step:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 1: create a new API Gateway for the  &lt;code&gt;user&lt;/code&gt; service&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We began by creating a new API Gateway for the &lt;code&gt;user&lt;/code&gt; service. Since this was essentially a new API, we also had to configure a new &lt;code&gt;Cognito&lt;/code&gt; authorizer to handle authentication for this service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverlessConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AwsConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Serverless&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;servcice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&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;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;httpApi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;allowedOrigins&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;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,],&lt;/span&gt;
          &lt;span class="na"&gt;allowedHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&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;Origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;allowedMethods&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;GET&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;POST&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;PATCH&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;DELETE&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;PUT&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;OPTIONS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;exposedResponseHeaders&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;apigw-requestid&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;authorizers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;httpUserApiAuthorizer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;identitySource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$request.header.Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;audience&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;${self:custom.cognitoUserPoolClientId}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;issuerUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${self:custom.issuerUrl}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cognitoUserPoolClientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CognitoUserPoolClientId-${sls:stage}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;issuerUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CognitoUserPoolIssuerUrl-${sls:stage}&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 goal here is not to explain how to set-up a Cognito authorization, if you want to learn about it you can read &lt;a href="https://dev.to/slsbytheodo/learn-serverless-on-aws-authentication-with-cognito-19bo"&gt;this article&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 2: Link service lambdas to the new API gateway&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The next step involved linking all of the Lambdas in the &lt;code&gt;user&lt;/code&gt; service to this new API Gateway. Serverless Framework does it for us when we declare the API in the configuration (step 1). However, we still need to set the new authorizer on our lambdas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handler.main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;events&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;httpApi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/user/create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// authorizer: { name: 'httpApiAuthorizer' }, &amp;lt; previous authorizer&lt;/span&gt;
        &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="na"&gt;authorizer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;httpUserApiAuthorizer&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="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;h3&gt;
  
  
  &lt;strong&gt;Step 3: Update API routes&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We then prefixed all API routes for the &lt;code&gt;user&lt;/code&gt; service with a specific route : &lt;code&gt;/user&lt;/code&gt;. This allowed our CloudFront distribution to easily differentiate between services based on the URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handler.main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;events&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;httpApi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/user/create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// make sure all user routes are prefixed&lt;/span&gt;
        &lt;span class="na"&gt;authorizer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;httpUserApiAuthorizer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Step 4: Update frontend API calls&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;On the frontend, we updated all API calls related to the &lt;code&gt;users&lt;/code&gt; service by adding the &lt;code&gt;/users&lt;/code&gt; prefix. This ensured that requests were routed to the correct API Gateway without any confusion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userList&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users/list&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;h3&gt;
  
  
  &lt;strong&gt;Step 5: Redirect Service Requests via CloudFront&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Finally, we set up a redirection rule in CloudFront. Any requests starting with &lt;code&gt;/service2&lt;/code&gt; were routed to the new API Gateway.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CloudFrontApiDistribution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AwsConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CloudFormationResource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::CloudFront::Distribution&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;DistributionConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Enabled&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;HttpVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Origins&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;DomainName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Join&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${self:custom.httpBooksApiId}&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;.execute-api.&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;${self:provider.region}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.amazonaws.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="c1"&gt;// Id: 'BackendAPI', &amp;lt;- previous global API name&lt;/span&gt;
          &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BooksAPI&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="na"&gt;OriginPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;CustomOriginConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;OriginProtocolPolicy&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-only&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="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;DomainName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Join&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${self:custom.httpUsersApiId}&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;.execute-api.&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;${self:provider.region}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.amazonaws.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UsersAPI&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;OriginPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;CustomOriginConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;OriginProtocolPolicy&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-only&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="o"&gt;**&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;PriceClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PriceClass_All&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ViewerCertificate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;CloudFrontDefaultCertificate&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;DefaultCacheBehavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// TargetOriginId: 'BooksAPI', &amp;lt;- previous global API name&lt;/span&gt;
        &lt;span class="na"&gt;TargetOriginId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BooksAPI&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="na"&gt;CacheBehaviors&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;PathPattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users/*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;TargetOriginId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UsersAPI&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="o"&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 default cache behaviors redirects all other routes to the original API, which is a keep-safe for all our other routes, because we don’t need to take care of them as they are already routed. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The benefits of this approach&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;By splitting the API and allocating different services to their own API Gateways, we gained several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modular growth&lt;/strong&gt;: Each service can now grow independently, with its own API Gateway, reducing the risk of hitting the 300 integration limit again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal frontend changes&lt;/strong&gt;: Thanks to CloudFront, the frontend barely needed any modifications. The redirection was handled transparently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service isolation&lt;/strong&gt;: Each service can now be managed and deployed independently, allowing for greater flexibility and scalability.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The AWS API Gateway limit of 300 integrations per API can be a hidden bottleneck, especially in fast-growing projects when you are unaware of its existence. In order to spot in before it arrives, I am adding a new rule on the &lt;a href="https://www.sls-mentor.dev/" rel="noopener noreferrer"&gt;sls-mentor&lt;/a&gt; tool, in order to be warned when you reach 250 integrations.&lt;/p&gt;

&lt;p&gt;While initially daunting, addressing it through a multi-API Gateway approach can keep your architecture flexible, scalable, and modular. This experience reinforced the importance of periodically reviewing architectural decisions and splitting services when they become too large. From now on, when I have an architecture with micro services, I will be creating a micro API for each service.&lt;/p&gt;

&lt;p&gt;For teams scaling on AWS, this approach ensures that when your services grow to the point of hitting such limits, you have a clear path forward without drastic changes to the frontend or overall application flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/slsbytheodo/sls-mentor-your-serverless-quality-teacher-has-arrived-5fin"&gt;sls-mentor and how to use it&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cover photo : by &lt;a href="https://unsplash.com/@arthurbizkit?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Arthur Mazi&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/black-and-red-arch-tunnel-KLAPnoLrpOE?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>apigateway</category>
    </item>
    <item>
      <title>Top 10 common errors I wish I hadn’t made using SQS</title>
      <dc:creator>Corentin Doue</dc:creator>
      <pubDate>Thu, 13 Jun 2024 11:56:11 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/top-10-common-errors-i-wish-i-hadnt-made-using-sqs-3jg2</link>
      <guid>https://dev.to/slsbytheodo/top-10-common-errors-i-wish-i-hadnt-made-using-sqs-3jg2</guid>
      <description>&lt;p&gt;Amazon SQS is a powerful service for messaging in traditional or Serverless applications, but it comes with its own set of challenges. I've compiled a list of common mistakes and best practices to help you navigate SQS more effectively. I also released the &lt;a href="https://www.swarmion.dev/docs/how-to-guides/use-serverless-contracts/sqs"&gt;Swarmion SQS contract&lt;/a&gt; that helps you avoid these pitfalls and focus on your business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR;
&lt;/h2&gt;

&lt;p&gt;Configuration&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Don’t expect SQS messages to be consumed in the order they are sent&lt;/li&gt;
&lt;li&gt;❌ Don’t set a too small &lt;em&gt;Retention Period&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;❌ Don’t set a too small &lt;em&gt;Visibility Timeout&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;❌ Don’t use Lambda reserved concurrency to control throughput&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Producer&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Don’t send messages that the consumer can’t process&lt;/li&gt;
&lt;li&gt;❌ Don’t send messages individually&lt;/li&gt;
&lt;li&gt;❌ Don’t send too many messages to &lt;code&gt;SendMessageBatchCommand&lt;/code&gt; or batch too big messages&lt;/li&gt;
&lt;li&gt;❌ Don’t forget to handle &lt;code&gt;SendMessageBatchCommand&lt;/code&gt; results&lt;/li&gt;
&lt;li&gt;❌ Don’t send too many messages to SQS FIFO queues&lt;/li&gt;
&lt;li&gt;❌ Don’t forget to use &lt;code&gt;MessageGroupeId&lt;/code&gt; for SQS FIFO Queues&lt;/li&gt;
&lt;li&gt;❌ Don’t use &lt;code&gt;uuid()&lt;/code&gt; as &lt;code&gt;MessageDeduplicationId&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consumer&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Don’t throw errors while processing a batch of SQS messages&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🤔 Yeah, there are 12. But a top 10 title sounded better&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  SQS Configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🙅 Error: Expecting messages to be consumed in the order they are sent
&lt;/h3&gt;

&lt;p&gt;💥 Impact: 🐛 Stability. Messages are processed in an unpredictable order.&lt;/p&gt;

&lt;p&gt;✅ Solution: Use SQS FIFO if you need to process messages in a precise order&lt;/p&gt;




&lt;h3&gt;
  
  
  🙅 Error: Setting a too small &lt;em&gt;Retention Period&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;💥 Impact: 🐛 Stability. Messages can be deleted before they are processed, especially with delays or multiple retries. This can be a debugging nightmare.&lt;/p&gt;

&lt;p&gt;✅ Solution: Set a generous retention period if you plan to use delays or retries. Retention is not billed.&lt;/p&gt;




&lt;h3&gt;
  
  
  🙅 Error: Setting a too small &lt;em&gt;Visibility Timeout&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;💥 Impact: 🐛 Stability. Messages can be processed several times if their visibility timeout expires before their first processor delete them.&lt;/p&gt;

&lt;p&gt;✅ Solution: AWS recommends setting a visibility timeout three time longer than the expected message processing duration.&lt;/p&gt;




&lt;h3&gt;
  
  
  🙅 Error: Using Lambda reserved concurrency to control throughput
&lt;/h3&gt;

&lt;p&gt;💥 Impact: 🐛 Stability. &lt;a href="https://www.youtube.com/watch?v=MCDEBA7asww"&gt;Messages can be lost due to throttle errors returned by the Lambda service&lt;/a&gt;, which can result in them being sent to the DLQ without being processed.&lt;/p&gt;

&lt;p&gt;✅ Solution: Use the &lt;code&gt;MaxConcurency&lt;/code&gt; parameter of the event source mapping instead of Lambda reserved concurrency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Producer
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🙅 Error: Sending messages that the consumer can’t process
&lt;/h3&gt;

&lt;p&gt;💥 Impact: 🐛 Stability. Consumers will throw errors, causing messages to be lost or behave unpredictably.&lt;/p&gt;

&lt;p&gt;✅ Solution: Enforce a strong interface between your producers and consumers.&lt;/p&gt;

&lt;p&gt;💡You can use &lt;a href="https://www.swarmion.dev/docs/why-swarmion/serverless-contracts/concepts"&gt;Swarmion contracts&lt;/a&gt; to create and enforce interfaces between your lambdas and the services that use them.&lt;/p&gt;




&lt;h3&gt;
  
  
  🙅 Error: Sending messages individually
&lt;/h3&gt;

&lt;p&gt;💥 Impact: ⚡💰 Performance and cost. Each message is sent as an HTTP request, increasing both time and cost.&lt;/p&gt;

&lt;p&gt;✅ Solution: Use &lt;code&gt;SendMessageBatchCommand&lt;/code&gt; to batch messages up to 10. One batch request is billed as one request.&lt;/p&gt;

&lt;p&gt;💡You can use the &lt;a href="https://www.swarmion.dev/docs/how-to-guides/use-serverless-contracts/sqs#build-a-typed-sendmessages-function"&gt;&lt;code&gt;sendMessages&lt;/code&gt; utility of Swarmion SQS contract&lt;/a&gt; to send multiple messages without bothering with technical aspects&lt;/p&gt;




&lt;h3&gt;
  
  
  🙅 Error: Sending too many messages to &lt;code&gt;SendMessageBatchCommand&lt;/code&gt; or batch too large messages
&lt;/h3&gt;

&lt;p&gt;💥 Impact: 🐛 Stability. &lt;code&gt;SendMessageBatchCommand&lt;/code&gt; can batch up to 10 messages with a total size below 256Kb. Exceeding these limits will cause the batch to be rejected, potentially losing messages.&lt;/p&gt;

&lt;p&gt;✅ Solution: Batch messages up to 10, ensuring the total size is within limits.&lt;/p&gt;

&lt;p&gt;💡 The &lt;a href="https://www.swarmion.dev/docs/how-to-guides/use-serverless-contracts/sqs#build-a-typed-sendmessages-function"&gt;&lt;code&gt;sendMessages&lt;/code&gt; utility of Swarmion SQS contract&lt;/a&gt; provides automatic batching that follow these rules. Just pass an array of messages and it handles the rest.&lt;/p&gt;




&lt;h3&gt;
  
  
  🙅 Error: Forgetting to handle &lt;code&gt;SendMessageBatchCommand&lt;/code&gt; results
&lt;/h3&gt;

&lt;p&gt;💥 Impact: 🐛 Stability. &lt;code&gt;SendMessageBatchCommand&lt;/code&gt; doesn’t throw if messages are throttled, they are returned in the response.&lt;/p&gt;

&lt;p&gt;✅ Solution: Handle failed batch items returned by &lt;code&gt;SendMessageBatchCommand&lt;/code&gt; and retry them.&lt;/p&gt;

&lt;p&gt;💡The &lt;a href="https://www.swarmion.dev/docs/how-to-guides/use-serverless-contracts/sqs#build-a-typed-sendmessages-function"&gt;&lt;code&gt;sendMessages&lt;/code&gt; utility of Swarmion SQS contract&lt;/a&gt; automatically retries throttled messages and throws errors by default to avoid silent bugs.&lt;/p&gt;




&lt;h3&gt;
  
  
  🙅 Error: Sending too many messages to SQS FIFO queues
&lt;/h3&gt;

&lt;p&gt;💥 Impact: 🐛 Stability. &lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/quotas-messages.html"&gt;FIFO queues are throttled at 300 requests per second&lt;/a&gt;, causing some messages to be lost if not handled.&lt;/p&gt;

&lt;p&gt;✅ Solution: Use high throughput FIFO queues or/and control the throughput rate of your sender.&lt;/p&gt;

&lt;p&gt;💡The &lt;a href="https://www.swarmion.dev/docs/how-to-guides/use-serverless-contracts/sqs#build-a-typed-sendmessages-function"&gt;&lt;code&gt;sendMessages&lt;/code&gt; utility of Swarmion SQS contract&lt;/a&gt; provides a &lt;code&gt;throughputCallsPerSecond&lt;/code&gt; parameter to precisely control throughput.&lt;/p&gt;




&lt;h3&gt;
  
  
  🙅 Error: Forgetting to use &lt;code&gt;MessageGroupeId&lt;/code&gt; for SQS FIFO Queues
&lt;/h3&gt;

&lt;p&gt;💥 Impact: ⚡Performance. All messages will be processed one at the time.&lt;/p&gt;

&lt;p&gt;✅ Solution: use &lt;code&gt;MessageGroupeId&lt;/code&gt; to enable parallel processing of message groups. Group messages by related usage to allow unrelated messages to be processed in parallel.&lt;/p&gt;




&lt;h3&gt;
  
  
  🙅 Error: Using &lt;code&gt;uuid()&lt;/code&gt; as &lt;code&gt;MessageDeduplicationId&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;💥 Impact: 🐛 Stability. Messages can be processed multiple times.&lt;/p&gt;

&lt;p&gt;✅ Solution: &lt;code&gt;MessageDeduplicationId&lt;/code&gt; must be a hash of your message content&lt;/p&gt;




&lt;h2&gt;
  
  
  Consumer
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🙅 Error: Throwing errors while processing a batch of SQS messages
&lt;/h3&gt;

&lt;p&gt;💥 Impact: ⚡🐛 Performance and stability. The entire batch will be retried after the visibility timeout is reached. Some messages will be processed or partially processed multiple times. As no message is deleted, this jam the queue.&lt;/p&gt;

&lt;p&gt;✅ Solution: Catch errors individually and delete successfully processed messages. With lambda event source mapping, use &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfailurereporting"&gt;&lt;code&gt;ReportBatchItemFailures&lt;/code&gt; function response type&lt;/a&gt; and send back the unprocessed &lt;code&gt;messageIds&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;💡You can use the &lt;a href="https://www.swarmion.dev/docs/how-to-guides/use-serverless-contracts/sqs#generate-the-lambda-handler"&gt;&lt;code&gt;getHandler&lt;/code&gt; utility of Swarmion SQS contract&lt;/a&gt; to generate a wrapper around your handler to process messages individually, catch errors and report failed messages to SQS.&lt;/p&gt;




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

&lt;p&gt;I hope these insights help you avoid the common mistakes I’ve encountered while working with SQS. Please share your experiences and any other tips you have for using SQS effectively.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>webdev</category>
      <category>sqs</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Your AWS app in depth like never before with sls-mentor</title>
      <dc:creator>Pierre Chollet</dc:creator>
      <pubDate>Tue, 11 Jun 2024 15:00:36 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/your-aws-app-in-depth-like-never-before-with-sls-mentor-1dgf</link>
      <guid>https://dev.to/slsbytheodo/your-aws-app-in-depth-like-never-before-with-sls-mentor-1dgf</guid>
      <description>&lt;h2&gt;
  
  
  Your serverless app like you've never seen it before with sls-mentor
&lt;/h2&gt;

&lt;p&gt;Ever dreamed of being able to visualize and analyze your entire AWS application at a glance? With the new 3.0 (alpha) of sls-mentor, it is now possible!&lt;/p&gt;

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

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



&lt;/p&gt;

&lt;p&gt;sls-mentor is a &lt;strong&gt;free&lt;/strong&gt; and &lt;strong&gt;open-source&lt;/strong&gt; tool that generates an interactive graph of your AWS application. This graph contains all the interactions between components of your app (Lambda functions, DynamoDB tables, S3 buckets...).&lt;/p&gt;

&lt;p&gt;sls-mentor also has a brand new feature: &lt;strong&gt;Dashboards&lt;/strong&gt;. With dashboards, you have access to stats about your app such as :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda Cold start duration 🏎&lt;/li&gt;
&lt;li&gt;Lambda Bundle size 📦&lt;/li&gt;
&lt;li&gt;S3 bucket size 🪣&lt;/li&gt;
&lt;li&gt;DynamoDB table size 📊&lt;/li&gt;
&lt;li&gt;And more to come! ✨&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Fsls-mentor%2Fdashboard%2Fassets%2Flambda.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Fsls-mentor%2Fdashboard%2Fassets%2Flambda.png" alt="Lambda Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Fsls-mentor%2Fdashboard%2Fassets%2Ftable.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Fsls-mentor%2Fdashboard%2Fassets%2Ftable.png" alt="Table Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to run sls-mentor?
&lt;/h2&gt;

&lt;p&gt;You only need your CLI to run the new 3.0 of sls-mentor, simply use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx sls-mentor@alpha &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;AWS_CLI_PROFILE&amp;gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &amp;lt;AWS_REGION&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/sls-mentor/sls-mentor" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Star sls-mentor on Github ⭐️&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;sls-mentor will perform its analysis live, on the AWS Account associated with the CLI profile.&lt;/p&gt;

&lt;p&gt;There are also filtering options: &lt;code&gt;-c&lt;/code&gt; to specify cloudformation stacks, &lt;code&gt;-t&lt;/code&gt; for tags&lt;/p&gt;

&lt;h2&gt;
  
  
  We need you!
&lt;/h2&gt;

&lt;p&gt;If you enjoyed trying sls-mentor 3.0, your feedback is valuable! Feel free to comment or to contact me on twitter&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/PierreChollet22" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Contact me on twitter 🚀&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;We are also open to contributions!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/sls-mentor/sls-mentor" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Contribute on Github ⭐️&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Free dynDNS for your NAS: auto-update DNS with your latest IP</title>
      <dc:creator>Guillaume Égée</dc:creator>
      <pubDate>Tue, 12 Mar 2024 12:38:00 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/auto-update-dns-a-record-with-the-latest-ip-of-your-nas-5g81</link>
      <guid>https://dev.to/slsbytheodo/auto-update-dns-a-record-with-the-latest-ip-of-your-nas-5g81</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;🎉 No more use a costly dynDNS service to update DNS A record when your IP changes!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've just set up a NAS at home and linked a domain name to my router IP. But one day I could no more access to it, because the router IP changed without any notice!&lt;br&gt;
Fortunately, a lot of dynDNS tools exist, but they are often paid services (even if quite cheap, I acknowledge). Why not building your own free dynDNS?&lt;/p&gt;

&lt;p&gt;I created a &lt;a href="https://github.com/guiyom-e/auto-update-ip-aws"&gt;&lt;strong&gt;repository&lt;/strong&gt;&lt;/a&gt; which rely on an AWS CDK stack to update your A record in Amazon Route 53 with an API endpoint. This endpoint can be called by a script whenever your IP changes.&lt;/p&gt;

&lt;p&gt;Suppose you host your own NAS server and want to access it at &lt;code&gt;my-server.com&lt;/code&gt; you have bought on &lt;a href="https://aws.amazon.com/getting-started/hands-on/get-a-domain/"&gt;AWS&lt;/a&gt;. You need to add a A record to make &lt;code&gt;my-server.com&lt;/code&gt; point to your server public IP. In general, if you rely on an Internet provider to get an IP, you are not guaranteed to have a one static, even if in practice they don't often change (at router reboot for instance). If you want to keep your A record up-to-date when your dynamic IP changes, this project is made for you!&lt;/p&gt;

&lt;p&gt;... and it is (almost) free!&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up the stack
&lt;/h2&gt;

&lt;p&gt;Just follow the &lt;a href="https://github.com/guiyom-e/auto-update-ip-aws#readme"&gt;installation steps&lt;/a&gt;!&lt;br&gt;
You'll need Node.js, an AWS account and a domain name of course!&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works?
&lt;/h2&gt;

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

&lt;p&gt;The script installed on your NAS will first call an API to retrieve its current IP, on a frequency defined with a cron job.&lt;br&gt;
If this IP has changed, it will call another API endpoint (protected with an API key this time). An express step functions will then update the record in Route53 if the requested IP is the same as the source IP (the actual IP of the server calling the endpoint).&lt;br&gt;
That's all!&lt;/p&gt;

&lt;h2&gt;
  
  
  🤑 Estimated cost
&lt;/h2&gt;

&lt;p&gt;The deployed stack uses AWS resources at really low cost, supposing the &lt;a href="//./scripts/domain-auto-update/update-ip.sh"&gt;script&lt;/a&gt; is run every hour and the IP changes every day:&lt;/p&gt;

&lt;p&gt;Check the IP (every hour)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;API Gateway V2&lt;/em&gt;: ~ $0.00083 = 31 days x 24h x 1.11/million requests. NB: free tier the first year&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Lambda&lt;/em&gt;: ~ $0.00126 / month = 31 days x 24h x 1000 milliseconds x $0.0000000017 (architecture ARM, memory: 128 MB, region eu-west-1, Jan. 2024)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Update the IP (every day)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Step Functions&lt;/em&gt;: ~ $0.033 / month = 31 days x ($0.000001 (price per request, Jan. 2024) + 1000 milliseconds x 64 MB x \$0.00001667 )&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;CloudWatch&lt;/em&gt;: Free tier = 6 months x 31 days x (2 kB (Step Functions) + 500 B (Lambda)) = 0.5 MB &amp;lt; 5 GB of free tier&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;API Gateway V1&lt;/em&gt;: ~ $0.00003 = 31 x 3.50/million requests. NB: free tier the first year&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Route 53&lt;/em&gt; : ~ $0.00001 / month = 31 queries x $0.40 per million queries&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;CloudFormation&lt;/em&gt; : free&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;IAM&lt;/em&gt;: free&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TOTAL COST: &lt;strong&gt;&amp;lt; $0.5 per year!&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NB: you need to add the domain name cost (~$12 per year (depending on the name chosen) + $0.50 per hosted zone per month).&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>dns</category>
      <category>aws</category>
      <category>route53</category>
      <category>stepfunctions</category>
    </item>
    <item>
      <title>Visualize your AWS app like never before with sls-mentor</title>
      <dc:creator>Pierre Chollet</dc:creator>
      <pubDate>Wed, 14 Feb 2024 12:33:19 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/visualize-your-aws-app-like-never-before-with-sls-mentor-2j3l</link>
      <guid>https://dev.to/slsbytheodo/visualize-your-aws-app-like-never-before-with-sls-mentor-2j3l</guid>
      <description>&lt;h2&gt;
  
  
  Your serverless app like you've never seen it before with sls-mentor
&lt;/h2&gt;

&lt;p&gt;Ever dreamed of being able to visualise your entire AWS application at a glance? With the new 3.0 (alpha) of sls-mentor, it is now possible!&lt;/p&gt;

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

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



&lt;/p&gt;

&lt;p&gt;sls-mentor is a &lt;strong&gt;free&lt;/strong&gt; and &lt;strong&gt;open-source&lt;/strong&gt; tool that generates an interactive graph of your AWS application. This graph contains all the interactions between components of your app (Lambda functions, DynamoDB tables, S3 buckets...), as well as their stats (coldstart duration, bundle size, table size...).&lt;/p&gt;

&lt;p&gt;The end-game of sls-mentor is to allow developers and tech-leads to be able to take informed decisions relative to the architecture of their AWS App, by providing them with user-friendly data and best-practices compatible with their situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to run sls-mentor?
&lt;/h2&gt;

&lt;p&gt;You only need your CLI to run the new 3.0 of sls-mentor, simply use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx sls-mentor@alpha &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;AWS_CLI_PROFILE&amp;gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &amp;lt;AWS_REGION&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/sls-mentor/sls-mentor" class="ltag_cta ltag_cta--branded"&gt;Star sls-mentor on Github ⭐️&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;sls-mentor will perform its analysis live, on the AWS Account associated with the CLI profile.&lt;/p&gt;

&lt;p&gt;There are also filtering options: &lt;code&gt;-c&lt;/code&gt; to specify cloudformation stacks, &lt;code&gt;-t&lt;/code&gt; for tags&lt;/p&gt;

&lt;h2&gt;
  
  
  What can sls-mentor do?
&lt;/h2&gt;

&lt;p&gt;sls-mentor 3.0 is still in alpha, but it is already quite capable!&lt;/p&gt;

&lt;h3&gt;
  
  
  Visualize a graph snapshot of your AWS app
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F348s7lfezlk6f7g7pduc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F348s7lfezlk6f7g7pduc.gif" alt="Image description" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some resources or interactions are not yet supported yet, we are working on them!&lt;/p&gt;

&lt;h3&gt;
  
  
  Rank Lambdas/DynamoDBs by stats (duration, size...)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqticz3xc65lgyb3io3f.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqticz3xc65lgyb3io3f.gif" alt="Image description" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More options coming soon!&lt;/p&gt;

&lt;h3&gt;
  
  
  Edit the graph
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqlry3vnc6gq0wqe6uhe.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqlry3vnc6gq0wqe6uhe.gif" alt="Image description" width="600" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have fun by moving around the components of your app, or pin them (by pressing SPACE) to make it clearer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Short-term roadmap
&lt;/h2&gt;

&lt;p&gt;We are currently working hard on making the alpha better!&lt;/p&gt;

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

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



&lt;/p&gt;

&lt;p&gt;Here are some of the features we want to implement during the following weeks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AppSync APIs and integrations&lt;/li&gt;
&lt;li&gt;Clustering (By Cloudformation stack, by tags...)&lt;/li&gt;
&lt;li&gt;Filtering options (By Cloudformation stack, by tags...)&lt;/li&gt;
&lt;li&gt;More stats (S3 buckets...)&lt;/li&gt;
&lt;li&gt;First best-practices&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  We need you!
&lt;/h2&gt;

&lt;p&gt;If you enjoyed trying sls-mentor 3.0, your feedback is valuable! Feel free to comment or to contact me on twitter &lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/PierreChollet22" class="ltag_cta ltag_cta--branded"&gt;Contact me on twitter 🚀&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;We are also open to contributions!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/sls-mentor/sls-mentor" class="ltag_cta ltag_cta--branded"&gt;Contribute on Github ⭐️&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>aws</category>
      <category>javascript</category>
      <category>serverless</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Migrating from API Gateway V2 to V1 to get REST API features</title>
      <dc:creator>Guillaume Égée</dc:creator>
      <pubDate>Tue, 13 Feb 2024 17:04:31 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/migrating-from-api-gateway-v2-to-v1-to-get-rest-api-features-nfp</link>
      <guid>https://dev.to/slsbytheodo/migrating-from-api-gateway-v2-to-v1-to-get-rest-api-features-nfp</guid>
      <description>&lt;p&gt;This guide aims to help you migrate a serverless or CloudFormation stack with API Gateway v2 (HTTP API) integrated with lambdas to the same stack using API Gateway v1 (REST API), with a focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handling API paths constraints&lt;/li&gt;
&lt;li&gt;Input and Output serialization (and in particular these breaking changes: JWT authentication, CORS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS REST API has a lot of features that HTTP API do not benefit from: cache at edge, WAF integration, API keys. Even if some features can be circumvented, like using CloudFront for cache at edge, some are not and a migration can be necessary. For a detailed comparison between the two versions, see this guide: &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 TLDR&lt;/strong&gt;: have a look to this &lt;a href="https://github.com/guiyom-e/sls-apigateway-v2-to-v1" rel="noopener noreferrer"&gt;repo&lt;/a&gt; for Typescript types, helpers to format API responses and a serverless template with the use of both API versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  🗜️ API path constraints are different
&lt;/h2&gt;

&lt;h3&gt;
  
  
  In REST API, each part of an API path is a resource
&lt;/h3&gt;

&lt;p&gt;In REST API, paths must be defined once, because each path part is a resource. This is not an issue with HTTP API.&lt;/p&gt;

&lt;p&gt;If you need to deploy &lt;code&gt;/actions/list&lt;/code&gt; and &lt;code&gt;/actions/toggle&lt;/code&gt;, you need to deploy 3 &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-resource.html" rel="noopener noreferrer"&gt;ApiGateway Resource&lt;/a&gt;, &lt;code&gt;/action&lt;/code&gt;, &lt;code&gt;/list&lt;/code&gt; and &lt;code&gt;/toggle&lt;/code&gt;, one for each path.&lt;br&gt;
If you define &lt;code&gt;/action&lt;/code&gt; twice, you will get &lt;code&gt;409 Conflict "Another resource with the same parent already has this name: actions"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With Serverless framework, it is done for you, except if you deploy different services with a path in common: in this case, you have no choice but to rename it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Variables in paths are managed differently
&lt;/h3&gt;

&lt;p&gt;Let’s say you have defined two routes &lt;code&gt;/network/{networkId}/actions/list&lt;/code&gt; and &lt;code&gt;/network/{userId}/profile&lt;/code&gt; in the HTTP API. Even if we can challenge this api path structure, it is possible with HTTP API… but not with REST API, because only one variable path part name is allowed.&lt;/p&gt;

&lt;p&gt;In CloudFormation, you should encounter this error: &lt;code&gt;"A sibling ({networkId}) of this resource already has a variable path part -- only one is allowed"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two options: either rename &lt;code&gt;userId&lt;/code&gt; to &lt;code&gt;networkId&lt;/code&gt; (if it makes sense 🤔) or change the path!&lt;/p&gt;
&lt;h2&gt;
  
  
  ➡️ Input is different
&lt;/h2&gt;

&lt;p&gt;There are two versions of the input payload, &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html" rel="noopener noreferrer"&gt;Payload 1.0 and Payload 2.0&lt;/a&gt;. For Typescript users, I wrote the &lt;a href="https://github.com/guiyom-e/sls-apigateway-v2-to-v1/blob/main/src/apiPayload.ts" rel="noopener noreferrer"&gt;payload types&lt;/a&gt;, as they are not exposed in AWS SDK.&lt;/p&gt;
&lt;h3&gt;
  
  
  Handling headers and query strings with multiple values
&lt;/h3&gt;

&lt;p&gt;With API Gateway v2, query strings and headers can have multiple values for the same key. In this case, values are comma-separated in the input object &lt;code&gt;headers&lt;/code&gt; or &lt;code&gt;queryStringParameters&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Payload 1.0 (REST API)&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="s2"&gt;headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;singleValueParam&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="s2"&gt;value1&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="s2"&gt;multiValueHeaders&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;multiValueParam&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value1&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="s2"&gt;value2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Payload 2.0 (HTTP API)&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="s2"&gt;headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;singleValueParam&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="s2"&gt;value1&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="s2"&gt;multiValueParam&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="s2"&gt;value1,value2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Authentication is different... and Cognito groups are formatted differently!
&lt;/h3&gt;

&lt;p&gt;If you use a Cognito authorizer, you can find similar keys in both payloads... but they are not the same!&lt;/p&gt;

&lt;p&gt;For instance for a user in Cognito groups "group1" and "group2":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Payload 1.0 (REST API)&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="s2"&gt;requestContext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;authorizer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claims&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cognito:groups&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="s2"&gt;group1,group2&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="s2"&gt;cognito:username&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="s2"&gt;bob@example.com&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="s2"&gt;email&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="s2"&gt;bob@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Payload 2.0 (HTTP API)&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="s2"&gt;requestContext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;authorizer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jwt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claims&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cognito:groups&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="s2"&gt;[group1 group2]&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="s2"&gt;cognito:username&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="s2"&gt;bob@example.com&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="s2"&gt;email&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="s2"&gt;bob@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="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;Therefore, Cognito groups must be parsed differently!&lt;/p&gt;

&lt;h3&gt;
  
  
  Other parameters are not at the same place...
&lt;/h3&gt;

&lt;p&gt;.. but I won't go through other differences, but they are a lot that can be easily found in &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html" rel="noopener noreferrer"&gt;these types&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔀 In REST API, CORS are handled at method level
&lt;/h2&gt;

&lt;p&gt;In HTTP API, a global configuration can be set ; in REST API it is at method level.&lt;br&gt;
So when moving to REST API, you need to edit every method to add CORS for preflight requests. And for other requests, you need to update the output of the lambda manually (see next section).&lt;/p&gt;

&lt;h2&gt;
  
  
  ⬇️ Output is different, even if a Lambda integration is chosen
&lt;/h2&gt;

&lt;p&gt;With HTTP API, if no &lt;code&gt;statusCode&lt;/code&gt; key is defined, the output payload is automatically stringified to a response body in a HTTP response with  status 200 (see &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.v2" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;)&lt;br&gt;
. This is not the case in REST API, so you need to format your output manually. For an easier migration, you can set up a middleware based on this &lt;a href="https://github.com/guiyom-e/sls-apigateway-v2-to-v1/blob/main/src/apiResponseFormatter.ts" rel="noopener noreferrer"&gt;code&lt;/a&gt;, or if you use &lt;code&gt;@middy&lt;/code&gt; middleware, based on this &lt;a href="https://github.com/guiyom-e/sls-apigateway-v2-to-v1/blob/main/src/apiResponseFormatterMiddleware.ts" rel="noopener noreferrer"&gt;code&lt;/a&gt;.&lt;br&gt;
These helpers also add CORS headers, as this is not handled by REST API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finally, should I move to REST API?
&lt;/h2&gt;

&lt;p&gt;In short, the REST API comes with more features, but is more complex whereas HTTP API is a &lt;a href="https://aws.amazon.com/api-gateway/pricing/" rel="noopener noreferrer"&gt;three-times cheaper&lt;/a&gt; and turnkey solution, especially if you use a JWT authentication and need CORS headers. Moving from one API to another is clearly possible, but painful for a big application with multiple services sharing the same API Gateway.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>apigateway</category>
      <category>serverless</category>
      <category>lambda</category>
    </item>
    <item>
      <title>Learn serverless on AWS step-by-step - Schedule tasks with EventBridge Scheduler</title>
      <dc:creator>Pierre Chollet</dc:creator>
      <pubDate>Tue, 16 Jan 2024 14:12:20 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/learn-serverless-on-aws-step-by-step-schedule-tasks-with-eventbridge-scheduler-4cbh</link>
      <guid>https://dev.to/slsbytheodo/learn-serverless-on-aws-step-by-step-schedule-tasks-with-eventbridge-scheduler-4cbh</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;In this series, I try to explain the basics of serverless on AWS, to enable you to build your own serverless applications. With &lt;a href="https://dev.to/slsbytheodo/learn-serverless-on-aws-step-by-step-lambda-destinations-f5b"&gt;last article&lt;/a&gt;, we discovered how to Lambda function destinations to avoid losing data when an asynchronous Lambda function fails. In this article, we will discover how to schedule tasks with EventBridge Scheduler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What will we do today?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a small memo application, where we can create a memo, and execute it at a specific date and time&lt;/li&gt;
&lt;li&gt;Create a Lambda function that creates a memo&lt;/li&gt;
&lt;li&gt;Create a Lambda function that is triggered at a specific date and time, and executes the memo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture of our application will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Flearn-serverless%2Fscheduler%2Fassets%2Farchitecture.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Flearn-serverless%2Fscheduler%2Fassets%2Farchitecture.png" title="Architecture of our application" alt="architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⬇️ I post serverless content very regularly, if you want more ⬇️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/PierreChollet22" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Follow me on twitter 🚀&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Quick announcement:&lt;/strong&gt; I also work on a library called &lt;a href="https://github.com/sls-mentor/sls-mentor" rel="noopener noreferrer"&gt;🛡 sls-mentor 🛡&lt;/a&gt;. It is a compilation of 40 serverless best-practices, that are automatically checked on your AWS serverless projects (no matter the framework). It is free and open source, feel free to check it out!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/sls-mentor/sls-mentor" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Find sls-mentor on Github ⭐️&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  How to schedule tasks with AWS EventBridge Scheduler?
&lt;/h2&gt;

&lt;p&gt;AWS EventBridge Scheduler is a service that allows you to schedule tasks in the future. It can be compared to AWS EventBridge Rules: while rules allow you to trigger tasks based on rates or cron expressions, scheduler allows you to trigger tasks at a specific date and time (they also support cron expressions and rates btw...).&lt;/p&gt;

&lt;p&gt;Using the AWS EventBridge Scheduler programmatically to trigger a Lambda function has an easy and a not-so-easy step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;😁 The easy step is to specify a target, this can easily be done using the ARN of the Lambda function you want to trigger&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🥵 The not-so-easy step is to give the permission to the scheduler to invoke your Lambda function. This is done by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a role with a &lt;code&gt;scheduler.amazonaws.com&lt;/code&gt; service principal (assumed by the scheduler)&lt;/li&gt;
&lt;li&gt;Giving the permission to the scheduler to invoke your Lambda function &lt;code&gt;lambda:InvokeFunction&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Giving the permission to your input Lambda function (&lt;code&gt;addMemo&lt;/code&gt;) to pass the role to the scheduler &lt;code&gt;iam:PassRole&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This can be summarized in the following diagram:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Flearn-serverless%2Fscheduler%2Fassets%2Fexecution-role.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Flearn-serverless%2Fscheduler%2Fassets%2Fexecution-role.png" title="Creation of an InvokeExecuteMemoRole" alt="scheduler-role"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's see how this works in practice, with a real code example!&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a small memo application using AWS EventBridge Scheduler
&lt;/h2&gt;

&lt;p&gt;To create our app, we will use the AWS CDK. If you are not familiar with it, I invite you to read the &lt;a href="https://dev.to/slsbytheodo/dont-miss-on-the-cloud-revolution-learn-serverless-on-aws-the-right-way-1kac"&gt;first article of this series&lt;/a&gt; where I explain properly how to setup a CDK project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cdk init app &lt;span class="nt"&gt;--language&lt;/span&gt; typescript
npm i @aws-sdk/client-scheduler
npm i uuid &lt;span class="c"&gt;# always useful to generate unique ids&lt;/span&gt;
npm i &lt;span class="nt"&gt;-D&lt;/span&gt; esbuild &lt;span class="c"&gt;# Needed to bundle our Lambdas!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, let's create the Infrastructure as Code (IAC) of the application. This is done by updating the CDK stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// stack.ts - Infrastructure as code&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;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib&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;Construct&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;constructs&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib&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;join&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;path&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;class&lt;/span&gt; &lt;span class="nc"&gt;Part17SchedulerStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Lambda function triggered by scheduler&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;executeMemo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_lambda_nodejs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NodejsFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ExecuteMemo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;executeMemo.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_18_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;bundling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;externalModules&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;@aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Create role for scheduler to invoke executeMemo&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invokeExecuteMemoRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;InvokeMemoRole&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;assumedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ServicePrincipal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scheduler.amazonaws.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;invokeExecuteMemoRole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addToPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PolicyStatement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;actions&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;lambda:InvokeFunction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;executeMemo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functionArn&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Lambda function that schedules executeMemo&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addMemo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_lambda_nodejs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NodejsFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AddMemo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;addMemo.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_18_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;bundling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;externalModules&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;@aws-sdk&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;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;SCHEDULE_TARGET_ARN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;executeMemo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functionArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;SCHEDULE_ROLE_ARN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invokeExecuteMemoRole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roleArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Allow addMemo to create a scheduler&lt;/span&gt;
    &lt;span class="nx"&gt;addMemo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addToRolePolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PolicyStatement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;actions&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;scheduler:CreateSchedule&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;resources&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;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Allow addMemo to pass the invokeExecuteMemoRole to the scheduler&lt;/span&gt;
    &lt;span class="nx"&gt;addMemo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addToRolePolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PolicyStatement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;actions&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;iam:PassRole&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;invokeExecuteMemoRole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roleArn&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Trigger addMemo via API Gateway&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RestApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;restApiName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Part17Service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;addMemo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addMemo&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;What is happening here?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1️⃣ We create the executeMemo Lambda function, that will be triggered by the scheduler&lt;/li&gt;
&lt;li&gt;2️⃣ We create a role that will be assumed by the scheduler, and that will allow it to invoke the executeMemo Lambda function (see intro diagram)&lt;/li&gt;
&lt;li&gt;3️⃣ We create the addMemo Lambda function, that will create a scheduler&lt;/li&gt;
&lt;li&gt;4️⃣ We allow the addMemo Lambda function to create a scheduler by adding the &lt;code&gt;scheduler:CreateSchedule&lt;/code&gt; permission and the &lt;code&gt;iam:PassRole&lt;/code&gt; (see intro diagram) permission to the addMemo role&lt;/li&gt;
&lt;li&gt;5️⃣ We create an API Gateway endpoint that will trigger the addMemo Lambda function, and a POST method to trigger it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's create the Lambda functions that will be triggered by the API Gateway endpoint. First, let's create the &lt;code&gt;addMemo&lt;/code&gt; Lambda function, that will create a schedule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// addMemo.ts - Lambda function that creates a scheduler&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;ActionAfterCompletion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;CreateScheduleCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FlexibleTimeWindowMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SchedulerClient&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-scheduler&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;v4&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;uuidv4&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;uuid&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="nc"&gt;SchedulerClient&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;scheduleTargetArn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SCHEDULE_TARGET_ARN&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&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;scheduleRoleArn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SCHEDULE_ROLE_ARN&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&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;scheduleTargetArn&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;scheduleRoleArn&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Missing environment variables&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;memo&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="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;timezone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Europe/Paris&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&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;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;date&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;time&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;memo&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;date&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bad Request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CreateScheduleCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;Target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Arn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;scheduleTargetArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;RoleArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;scheduleRoleArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Input&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="nx"&gt;memo&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;ScheduleExpressionTimezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ScheduleExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`at(&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;T&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;time&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="na"&gt;FlexibleTimeWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FlexibleTimeWindowMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;ActionAfterCompletion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ActionAfterCompletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DELETE&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="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Memo scheduled&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;What is happening here?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1️⃣ We setup a client and parse environment variables (see IAC where we set them)&lt;/li&gt;
&lt;li&gt;2️⃣ We parse the body of the request, and extract the memo, date, time and timezone (default to Europe/Paris (where I live 😅))&lt;/li&gt;
&lt;li&gt;3️⃣ We create a schedule using the AWS SDK for Javascript

&lt;ul&gt;
&lt;li&gt;🅰️ We set the target, using the &lt;code&gt;scheduleTargetArn&lt;/code&gt; and &lt;code&gt;scheduleRoleArn&lt;/code&gt; environment variables&lt;/li&gt;
&lt;li&gt;🅱️ We set the schedule expression, using the date and time provided in the request, along with the timezone, and we set the schedule to be automatically deleted after execution&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Finally, let's create the &lt;code&gt;executeMemo&lt;/code&gt; Lambda function, that will be triggered by the scheduler. This function will simply log the memo to the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// executeMemo.ts - Lambda function that executes a memo&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;memo&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;memo&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&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="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;memo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&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;Very easy! Notice that the lambda input is the same as the &lt;code&gt;input&lt;/code&gt; field specified in the &lt;code&gt;CreateScheduleCommand&lt;/code&gt; of the &lt;code&gt;addMemo&lt;/code&gt; Lambda function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test our app
&lt;/h2&gt;

&lt;p&gt;We are done! Time to deploy and to test our API route &lt;code&gt;/addMemo&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run cdk bootstrap
npm run cdk deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Flearn-serverless%2Fscheduler%2Fassets%2Fadd-memo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Flearn-serverless%2Fscheduler%2Fassets%2Fadd-memo.png" title="Add memo Postman request" alt="add memo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I live in Paris 🇫🇷, so I used my default &lt;code&gt;Europe/Paris&lt;/code&gt; timezone, but you can specify the timezone of your choice. I specified a time of 22:37, and it is 22:36, if I head to Cloudwatch, I should see my executeMemo Lambda function being triggered in 1 minute.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Flearn-serverless%2Fscheduler%2Fassets%2Fexecute-memo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Flearn-serverless%2Fscheduler%2Fassets%2Fexecute-memo.png" title="Execute Memo execution in Cloudwatch" alt="cloudwatch"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It worked! I can also see the payload of the event, and it contains the memo I created.&lt;/p&gt;

&lt;h3&gt;
  
  
  Going further
&lt;/h3&gt;

&lt;p&gt;What could be improved in this app?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We could go further and add a &lt;code&gt;/getMemos&lt;/code&gt; route, that would return all the memos that are not executed yet&lt;/li&gt;
&lt;li&gt;We could also add a &lt;code&gt;/executeMemo&lt;/code&gt; route, that would execute a memo immediately, and cancel the corresponding schedule&lt;/li&gt;
&lt;li&gt;As always, implementing authentication would be a good idea 😅&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This article was a basic introduction to AWS EventBridge Scheduler. We discovered how to create a scheduled task, and how to execute it. We also discovered how to use the AWS CDK to provision our infrastructure. I hope you enjoyed this article, and that you learned something new!&lt;/p&gt;

&lt;p&gt;I plan to continue this &lt;a href="https://dev.to/pchol22/series/22030"&gt;series of articles&lt;/a&gt; on a bi-monthly basis. You can follow this progress on my &lt;a href="https://github.com/PChol22/learn-serverless" rel="noopener noreferrer"&gt;repository&lt;/a&gt;! I will cover new topics in the future, if you have any suggestions, do not hesitate to contact me!&lt;/p&gt;

&lt;p&gt;I would really appreciate if you could react and share this article with your friends and colleagues. It will help me a lot to grow my audience. Also, don't forget to subscribe to be updated when the next article comes out!&lt;/p&gt;

&lt;p&gt;I you want to stay in touch here is my &lt;a href="https://twitter.com/PierreChollet22" rel="noopener noreferrer"&gt;twitter account&lt;/a&gt;. I often post or re-post interesting stuff about AWS and serverless, feel free to follow me!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/PierreChollet22" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Follow me on twitter 🚀&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>🚀 Lambda Test Revolution: Master Mocking &amp; Slash Costs with HTTP-Interceptor!</title>
      <dc:creator>Maxime Vivier</dc:creator>
      <pubDate>Fri, 15 Dec 2023 12:47:11 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/lambda-test-revolution-master-mocking-slash-costs-with-http-interceptor-1i9l</link>
      <guid>https://dev.to/slsbytheodo/lambda-test-revolution-master-mocking-slash-costs-with-http-interceptor-1i9l</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR 📰
&lt;/h2&gt;

&lt;p&gt;Learn how to perform integration tests iso prod with aws serverless services. Using lambda-http-interceptor you can easily intercept and mock http calls coming from your deployed lambda functions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why should you use it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want to test your lambda functions in a iso-prod environment 🧪&lt;/li&gt;
&lt;li&gt;You want to save money while running integration tests by not triggering costly third party APIs 💸&lt;/li&gt;
&lt;li&gt;You want to control the behavior of third party APIs to test edge cases 🎯&lt;/li&gt;
&lt;li&gt;You don't want to change your lambda code to make it testable 👷&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And maybe you can use it for all theses reasons at the same time!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MaximeVivier/lambda-http-interceptor" class="ltag_cta ltag_cta--branded"&gt;Try lambda-http-interceptor here 😉&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Intercept http calls in lambda functions 🪝
&lt;/h2&gt;

&lt;p&gt;The lib is made of a CDK Construct to instantiate in your stack.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;HttpInterceptor&lt;/code&gt; &lt;strong&gt;construct needs to be instantiated in the stack&lt;/strong&gt;, or inside another Construct. And then &lt;strong&gt;the interceptor needs to be applied to the lambda function&lt;/strong&gt; http calls need to be intercepted from.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;applyHttpInterceptor&lt;/code&gt; uses &lt;code&gt;Aspects&lt;/code&gt; in order to apply it on each &lt;code&gt;NodeLambdaFunction&lt;/code&gt; it finds, thus &lt;code&gt;applyHttpInterceptor&lt;/code&gt; takes any Construct as input.&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;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-cdk-lib&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;Construct&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;constructs&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;HttpInterceptor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;applyHttpInterceptor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lambda-http-interceptor&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;NodejsFunction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-cdk-lib/aws-lambda-nodejs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-cdk-lib/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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;interceptor&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;HttpInterceptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HttpInterceptor&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;myLambdaFunctionThatMakesExternalCalls&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;NodejsFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MakeExternalCalls&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;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_18_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;index.handler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./handler.ts&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="nf"&gt;applyHttpInterceptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myLambdaFunctionThatDoesExternalCalls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;interceptor&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;After deploying, everything is setup on the stack to then perform integration tests.&lt;/p&gt;

&lt;p&gt;The second part of the lib is a set of &lt;strong&gt;tools to perform integration tests&lt;/strong&gt;. They are gathered in the &lt;code&gt;HttpLambdaInterceptorClient&lt;/code&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node-fetch&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;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest&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;HTTP_INTERCEPTOR_TABLE_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;table-name-from-construct&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HttpLambdaInterceptorClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lambda-http-interceptor&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;triggerMyLambdaFunctionThatMakesExternalCalls&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;./utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;interceptorClient&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;HttpLambdaInterceptorClient&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;lt;myLambdaFunctionThatMakesExternalCalls-name&amp;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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tests my lambda function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;interceptorClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createConfigs&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api-1/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&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;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}),&lt;/span&gt;
          &lt;span class="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;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;https://api-2/path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;passThrough&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;triggerMyLambdaFunctionThatMakesExternalCalls&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;interceptedCalls&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;interceptorClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pollInterceptedCalls&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;numberOfCallsToExpect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interceptedCalls&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&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;
  
  
  How does it work? 📚
&lt;/h2&gt;

&lt;h3&gt;
  
  
  lambda-http-interceptor stores the configurations and the call made in DynamoDB table 🏪
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;HttpInterceptor&lt;/code&gt; instantiates a DynamoDB table in the stack. The table is used to store the configurations of the http calls to intercept. When performing integration tests, filling up the table with configuration is done using the &lt;code&gt;createConfigs&lt;/code&gt; method of the &lt;code&gt;HttpLambdaInterceptorClient&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;Then assertions can be made on the calls made by the lambda after they are fetched using the &lt;code&gt;pollInterceptedCalls&lt;/code&gt; method of the &lt;code&gt;HttpLambdaInterceptorClient&lt;/code&gt; class.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Don't forget to give the user you're using to perform the integration tests the right to read in the table. In general, we use AdministratorAccess role for the user performing these tasks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  lambda-http-interceptor uses an internal extension to intercept http calls in lambda functions 📡
&lt;/h3&gt;

&lt;p&gt;The internal extension that the interceptor deploys on the lambda functions overrides the &lt;code&gt;http&lt;/code&gt; module of nodejs that is used to make http calls.&lt;/p&gt;

&lt;p&gt;For each call made by the lambda, it fetches the http calls configuration stored in DynamoDB and either passes through the call or returns the response value configured at the start.&lt;/p&gt;

&lt;p&gt;It keeps track of the http calls listed that are listed in the configuration. If the response of a call doesn't need to be changed but it still needs to be tracked in order to make assertions on it, the configuration of the call doesn't change and the response only contains &lt;code&gt;passthrough: true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want to deep dive the functioning of the interceptor, you can check out &lt;a href="https://dev.to/slsbytheodo/power-up-your-serverless-application-with-aws-lambda-extensions-3a31"&gt;this article&lt;/a&gt; that presents extensions really clearly using a simple example.&lt;/p&gt;

&lt;h2&gt;
  
  
  lambda-http-interceptor has everything built in 💪
&lt;/h2&gt;

&lt;p&gt;The setup is fairly easy and it can be used to make assertions on the calls made by your deployed lambda functions. And you don't edit the code of your lambda handler.&lt;/p&gt;

&lt;p&gt;The documentation on github is far more exhaustive to get you started.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MaximeVivier/lambda-http-interceptor" class="ltag_cta ltag_cta--branded"&gt;Try lambda-http-interceptor here 😉&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Don't hesitate to star it ⭐️&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>lambda</category>
      <category>integrationtests</category>
      <category>aws</category>
    </item>
    <item>
      <title>6 guidelines for risk-less data migrations</title>
      <dc:creator>Guillaume Égée</dc:creator>
      <pubDate>Wed, 13 Dec 2023 13:25:19 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/6-guidelines-for-risk-less-data-migrations-3idl</link>
      <guid>https://dev.to/slsbytheodo/6-guidelines-for-risk-less-data-migrations-3idl</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“The majority of project issues I have seen come from databases, whatever is the technology”&lt;/em&gt; said one day one my experienced engineering manager...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is why I have built 6 guidelines I try to follow when modifying data on tech projects. They are especially helpful when working with databases like DynamoDB with few tooling (no ORM, etc.).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Data is key in business… and it is where it often fails.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Almost every app has a &lt;strong&gt;database&lt;/strong&gt; with more or less structured data. These data items may evolve during app development, with the addition of new fields, changes to field structure, data updates, or removal of fields. These evolutions, often referred to as &lt;strong&gt;migrations&lt;/strong&gt;, are critical as they enable the app to evolve but can also introduce bugs if not executed properly.&lt;br&gt;
A data migration &lt;strong&gt;plan&lt;/strong&gt; is a perfect tool to mitigate the risks of these operations, as it avoids common pitfalls and ensures reliability.  &lt;/p&gt;

&lt;h2&gt;
  
  
  📋️ First thing: have a migration protocol defined
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Define a protocol to follow in advance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Define &lt;strong&gt;who&lt;/strong&gt; is responsible of what. You can use a responsibility matrix (&lt;a href="https://en.wikipedia.org/wiki/Responsibility_assignment_matrix"&gt;&lt;strong&gt;RACI&lt;/strong&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Define &lt;strong&gt;when&lt;/strong&gt; the migration should be planned&lt;/li&gt;
&lt;li&gt;Define &lt;strong&gt;what&lt;/strong&gt; should be done in precise steps (manually deploy a function, trigger a Continuous Deployment workflow, etc.) ?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Follow the protocol
&lt;/h3&gt;

&lt;p&gt;A simple advice... to avoid acting in panic if something goes wrong…&lt;/p&gt;

&lt;h3&gt;
  
  
  Upgrade the protocol with your learnings
&lt;/h3&gt;

&lt;p&gt;There are often recurring patterns in migrations: making a field non nullable, updating a data type, etc. To capitalize on them, build a list of common migrations with explanations of the best implementation strategy based on past experiences.&lt;/p&gt;

&lt;h2&gt;
  
  
  ↔️ Have a strategy to handle the transition state
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Estimate the duration and load&lt;/strong&gt; (scalability) of the migration process, in all environments
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;What specific cases should be anticipated in production?

&lt;ul&gt;
&lt;li&gt;What differences should be taken into account when estimating the migration sizing (database size, environment configuration)?&lt;/li&gt;
&lt;li&gt;Are there API/database quotas that you should plan for?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Apply a margin for the manual parts (launching the migration, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Migration strategies
&lt;/h3&gt;

&lt;p&gt;Given that estimation and business concerns, choose between these &lt;strong&gt;2 main strategies&lt;/strong&gt;:&lt;/p&gt;

&lt;h4&gt;
  
  
  💥 &lt;strong&gt;“Big Bang” Migration&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Only one version of a migration set (pieces of data to modify) can exist at application uptime.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plan a &lt;strong&gt;service interruption&lt;/strong&gt; when data will be unavailable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✍️ Example of a user-friendly service interruption: during the migration process, the frontend application displays a "maintenance banner", and the backend is programmatically locked, to ensure no side-effect can corrupt data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✳️ Pros: quicker method, advised when building a project with low risks if some data is lost/corrupted&lt;/li&gt;
&lt;li&gt;⚠️ Risks:

&lt;ul&gt;
&lt;li&gt;possibly long downtime,&lt;/li&gt;
&lt;li&gt;risk of database throttling (in case of naive parallel read/write implementation for instance)&lt;/li&gt;
&lt;li&gt;more difficult to write migrations with a reliable rollback&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  🌊 &lt;strong&gt;Migration in two (or more) steps&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Handle old and new versions inside the migration set.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data migration and new code deployment must be uncoupled and done in the proper order.&lt;/li&gt;
&lt;li&gt;Examples :

&lt;ul&gt;
&lt;li&gt;if you &lt;strong&gt;remove&lt;/strong&gt; a column (or a table, etc.), remove the code using this column and then migrate the data&lt;/li&gt;
&lt;li&gt;if you &lt;strong&gt;add&lt;/strong&gt; a column, migrate the data first and then deploy the code using it&lt;/li&gt;
&lt;li&gt;if you &lt;strong&gt;update&lt;/strong&gt; a column, add a new column and, after it has been fulfilled progressively, remove the old column&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;💡 You can run integration tests with both versions to ensure the application works in both cases&lt;/li&gt;
&lt;li&gt;✳️ Pros: reliable method, that is error proof (if the migration fails, the system can continue running without interruption)&lt;/li&gt;
&lt;li&gt;⚠️ Risks:

&lt;ul&gt;
&lt;li&gt;more complex to develop&lt;/li&gt;
&lt;li&gt;longer to fulfill completely&lt;/li&gt;
&lt;li&gt;risk of maintaining multiple versions over a long period of time.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔂 Make a plan reproducible in multiple environments
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Write idempotent migrations (i.e. a migration can be applied multiple times with the same result)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;There are many reasons for needing to re-apply a migration: migration partially completed, stopped or failed, etc.&lt;/li&gt;
&lt;li&gt;The migration script should work without any state-full input&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✍️ Simple example of &lt;strong&gt;idempotent migrations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;Bad&lt;/strong&gt;:   if you run the migration twice, the data will be corrupted.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform_migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;not_idem_potent_migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_version&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
          &lt;span class="nf"&gt;perform_migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;✅  &lt;strong&gt;Good&lt;/strong&gt;: even if the migration itself (incrementation) is not idem-potent, it is not possible to run it twice with this migration.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;idem_potent_migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;new_version&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nf"&gt;perform_migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Save the migration scripts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Committing migration scripts ensures the same process can be applied to another environment easily and with known and reproducible steps.&lt;/li&gt;
&lt;li&gt;In case of error, it will help understanding what happened.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Test your scenario
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Test the migration script (with unit tests) to verify its functionality.

&lt;ul&gt;
&lt;li&gt;Check that data items of the migration set are correctly selected&lt;/li&gt;
&lt;li&gt;Check all edge cases&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Use dedicated environments to test the migration plan

&lt;ul&gt;
&lt;li&gt;Non-production environments can help to find bugs, all the more if data is ISO-prod.&lt;/li&gt;
&lt;li&gt;If it is not possible to test a scenario with production data, you can at least run scripts to check for errors, or run migrations in dry-run&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📆 The plan includes communication with stakeholders
&lt;/h2&gt;

&lt;p&gt;Running a migration can have impacts on the underlying business, so it should not remain in the technical sphere, but be shared to stakeholders.&lt;/p&gt;

&lt;h3&gt;
  
  
  Communicate to business owners and stakeholders
&lt;/h3&gt;

&lt;p&gt;Explicit the risks of the migration and consider their concerns&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stakeholders should be involved in the decision to migrate data, as they know the system and are responsible of it.&lt;/li&gt;
&lt;li&gt;They may help find edge cases in the migration, like &lt;strong&gt;dependencies to other teams&lt;/strong&gt;, a &lt;strong&gt;specific business case&lt;/strong&gt; not handled, etc.&lt;/li&gt;
&lt;li&gt;✍️❌ &lt;strong&gt;Common pitfall&lt;/strong&gt;: Delete a deprecated column that is still used by another team&lt;/li&gt;
&lt;li&gt;✍️✅ &lt;strong&gt;Good practice&lt;/strong&gt;: Continue supporting legacy versions until they are no longer in use, and establish a deadline to cease support.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose the right moment to run the migration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✍️❌ &lt;strong&gt;Common pitfall&lt;/strong&gt;: Run a resource-consuming migration during peak hours (example: adding a non-nullable column of a table in PostgreSQL that will lock it).&lt;/li&gt;
&lt;li&gt;✍️✅ &lt;strong&gt;Good practice&lt;/strong&gt;: Run a “big bang” migration just after the deployment to reduce the service interruption lead time. Run it during off-peak hours and when a development team is available in case of error.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ↩️ A rollback strategy is defined
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Backup your data and practice restoring it
&lt;/h3&gt;

&lt;p&gt;If anything goes wrong, you should be &lt;em&gt;able to restore&lt;/em&gt; your database to a correct state.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud providers have services dedicated to backups (e.g. &lt;a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/whatisbackup.html"&gt;AWS Backup&lt;/a&gt; if you are using AWS, &lt;a href="https://www.actifio.com/"&gt;Actifio&lt;/a&gt; on GCP, etc.) and database systems often come with their own backup solution.&lt;/li&gt;
&lt;li&gt;Practice restoring your data&lt;/li&gt;
&lt;li&gt;Be pragmatic. Can you afford to restore your data?

&lt;ul&gt;
&lt;li&gt;Some data items might have been updated during the process&lt;/li&gt;
&lt;li&gt;Is it worth to rollback, if only a few items have an issue?&lt;/li&gt;
&lt;li&gt;What is the risk of a data loss?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Write a “down” migration, as well as the “up” migration… or at least a Plan B
&lt;/h3&gt;

&lt;p&gt;How many times I've heard (or said) "It won't fail, no need of plan B"... and it finally failed ?&lt;br&gt;
I won't go through implementation details here (and a lot of ORMs have dedicated tools for this), the main point is "Don't be overconfident"!&lt;/p&gt;

&lt;h2&gt;
  
  
  🧱 The plan includes a check that everything is ok in the end
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Keep track of migration states with versioning
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;If possible, save the changes and the version of items during the migration process.&lt;/li&gt;
&lt;li&gt;Keep track of migration applications in each environment.&lt;/li&gt;
&lt;li&gt;The versions must be strictly increasing (in a specific order relationship) and deterministic to be able to compare versions. For instance, with an incremented integer or a timestamp and a reference to the previous version.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Remove data after complete validation only
&lt;/h3&gt;

&lt;p&gt;Although, this does not guarantee that you will end up with a successful migration after the removal operation, this allows you to detect potential issues you have not forecast and facilitate reverting the changes.&lt;/p&gt;

&lt;p&gt;✍️ E.g. only remove ‘address’ after you have correctly added ‘street_name’, ‘city’, ‘postcode’.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check that everything is ok &lt;strong&gt;after&lt;/strong&gt; the migration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Audit the database after migration&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Use a monitoring system to keep track of new errors.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Not seeing code or monitoring issue do not mean there is no issue ! Check it with &lt;strong&gt;business owners&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You can set-up end-to-end tests to avoid regressions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🌟 What to remember?
&lt;/h2&gt;

&lt;p&gt;These 6 guidelines are just an attempt to sum-up what to care about when applying data migrations. They can also apply to application deployments or whatever operation that introduces a breaking change. But the main learning could also be &lt;strong&gt;capitalize on knowledge&lt;/strong&gt; to avoid reproducing the same mistakes !&lt;/p&gt;

&lt;p&gt;Want to share your own tips or tech convictions? Don't hesitate to comment on this post 😉&lt;/p&gt;

</description>
      <category>database</category>
      <category>dynamodb</category>
    </item>
    <item>
      <title>Override the 500 resource limit of AWS CloudFormation templates with Serverless Framework</title>
      <dc:creator>Anaïs Schlienger</dc:creator>
      <pubDate>Fri, 08 Dec 2023 14:47:45 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/override-the-resource-limit-of-aws-cloudformation-templates-with-serverless-framework-3bdh</link>
      <guid>https://dev.to/slsbytheodo/override-the-resource-limit-of-aws-cloudformation-templates-with-serverless-framework-3bdh</guid>
      <description>&lt;p&gt;AWS provides a robust infrastructure for deploying serverless applications using AWS CloudFormation. However, it's not uncommon to encounter resource limits when working on large serverless projects. One way to tackle this challenge is by splitting your CloudFormation stacks into smaller, more manageable units. AWS suggests themselves to use &lt;em&gt;nested stacks&lt;/em&gt; to overcome the 500-resource limit. In this article, we'll explore how to achieve this using the Serverless Framework and a plugin called &lt;code&gt;serverless-plugin-split-stacks&lt;/code&gt;. We'll also discuss some tips and tricks to make the process smoother.&lt;/p&gt;

&lt;p&gt;Nested stacks are a safe way to address the resource limit wall whilst keeping the benefits of a single stack. The main benefit of a single stack is that you can deploy and rollback all resources at once. As nested stacks have a dependency to their parent stack, so if one of the nested stacks fails to deploy, the parent stack will roll back to its previous state. The nested stacks also share the output variables (such as ids or arn). We keep an atomic behavior of our stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before we dive into the details, make sure you have the following in place:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AWS Account&lt;/li&gt;
&lt;li&gt;Node.js and npm installed&lt;/li&gt;
&lt;li&gt;Serverless Framework installed (&lt;code&gt;npm install -g serverless&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A Serverless Framework project set up&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Splitting Stacks with serverless-plugin-split-stacks
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Installation
&lt;/h4&gt;

&lt;p&gt;First, you need to install the &lt;code&gt;serverless-plugin-split-stacks&lt;/code&gt; plugin. Navigate to your Serverless project directory and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;serverless-plugin-split-stacks &lt;span class="nt"&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Configuration
&lt;/h4&gt;

&lt;p&gt;In your &lt;code&gt;serverless.ts&lt;/code&gt; (or &lt;code&gt;serverless.yml&lt;/code&gt;) file, add the plugin to the &lt;code&gt;plugins&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt; &lt;span class="nx"&gt;plugins&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;serverless-plugin-split-stacks&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 configure it under the &lt;code&gt;custom&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="nl"&gt;splitStacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;perFunction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;perType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;perGroupFunction&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;nestedStackCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&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;ul&gt;
&lt;li&gt;
&lt;code&gt;perFunction&lt;/code&gt;: Setting this to &lt;code&gt;true&lt;/code&gt; would split the stack for each AWS Lambda function. Setting it to &lt;code&gt;false&lt;/code&gt; keeps your structure manageable.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;perType&lt;/code&gt;: If you want to split stacks based on resource types (e.g., DynamoDB tables, S3 buckets), set this to &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;perGroupFunction&lt;/code&gt;: This option is set to &lt;code&gt;true&lt;/code&gt;, which splits stacks equally based groupings of functions (the lambda and all its associated ressources, eg. IAM roles).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nestedStackCount&lt;/code&gt;: disabled if not specified ; it controls how many resources are deployed in parallel.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stackConcurrency: number&lt;/code&gt;: disabled if not specified ; it controls how many stacks are deployed in parallel.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Renaming Existing Lambdas
&lt;/h4&gt;

&lt;p&gt;Here is the tricky part. The plugin doesn't work with existing resources. You can force the migration of an existing resource to a new stack if you use a custom migration with &lt;code&gt;force: true&lt;/code&gt; but it will delete the resource and recreate it. It might create conflicts in CloudFormation or issues with IAM. This is not a good idea for production environments.&lt;/p&gt;

&lt;p&gt;No worries, there is a workaround.&lt;/p&gt;

&lt;p&gt;If you have existing Lambdas, you need to rename them to follow the new structure. The easiest way is to suffix their names with an underscore &lt;code&gt;_&lt;/code&gt;. For example, if you had a Lambda named &lt;code&gt;myFunction&lt;/code&gt;, you can rename it to &lt;code&gt;myFunction_&lt;/code&gt;. You use any other suffix you like honestly, but the underscore is a good convention as it is easy to read and less bulky visually.&lt;/p&gt;

&lt;p&gt;Just like that, without being deleted, your lambdas are moved to the new stack.&lt;/p&gt;

&lt;h4&gt;
  
  
  Deploying the Stacks
&lt;/h4&gt;

&lt;p&gt;When deploying your Serverless project for the first time with these changes, ensure you set the &lt;code&gt;disableRollback&lt;/code&gt; parameter to &lt;code&gt;false&lt;/code&gt;. This way, if something goes wrong during deployment, AWS will not automatically roll back the changes. Here's how you can set it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sls deploy &lt;span class="nt"&gt;--disableRollback&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the initial deployment, it's recommended to set &lt;code&gt;disableRollback&lt;/code&gt; back to &lt;code&gt;true&lt;/code&gt; for safety reasons. This ensures that the stack rolls back to its previous state in case of deployment failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Let's illustrate these steps with a simple example. Consider a Serverless service with three functions: &lt;code&gt;userFunction&lt;/code&gt;, &lt;code&gt;orderFunction&lt;/code&gt;, and &lt;code&gt;paymentFunction&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install the &lt;code&gt;serverless-plugin-split-stacks&lt;/code&gt; plugin.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure your &lt;code&gt;serverless.ts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-serverless-app&lt;/span&gt;

&lt;span class="na"&gt;frameworkVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=2.50.0'&lt;/span&gt;
&lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;serverless-plugin-split-stacks&lt;/span&gt;

&lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodejs18.x&lt;/span&gt;

&lt;span class="na"&gt;custom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;splitStacks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;perFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;perType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;perGroupFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;nestedStackCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

&lt;span class="na"&gt;functions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;userFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;userFunction.handler&lt;/span&gt;
  &lt;span class="na"&gt;orderFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orderFunction.handler&lt;/span&gt;
  &lt;span class="na"&gt;paymentFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;paymentFunction.handler&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Rename existing Lambdas if necessary.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deploy the stacks as explained above.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  💭 Limitations
&lt;/h3&gt;

&lt;p&gt;Hitting the resource limit may be a sign that your application is becoming too complex. It's a good idea to review your architecture and see if you can simplify it or re-organize it. Having smaller services is easier to maintain and manage.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 Conclusion
&lt;/h3&gt;

&lt;p&gt;By splitting your AWS CloudFormation stacks using the &lt;code&gt;serverless-plugin-split-stacks&lt;/code&gt;, you can overcome the 500-resource limit and manage your serverless applications more effectively. This approach not only makes your infrastructure more scalable but also eases the maintenance of your serverless projects as they grow.&lt;/p&gt;

&lt;p&gt;However, sub-stacks have their limits too and it does not mean you can grow them out indefinitely. They have the same resource limit as the main stack, and the more nested stacks you have, the longer your stack takes to deploy.&lt;/p&gt;

&lt;p&gt;Having to split your stacks is a sign that your application is becoming too complex and you should review your architecture. One way to change the way you split your stacks is to have a stack for each decoupled business entity. Or maybe a micro-service architecture is maybe not the good fit for your use case: you can look into hexagonal architectures for example.&lt;/p&gt;

&lt;h4&gt;
  
  
  References
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html"&gt;AWS CloudFormation Limits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-nested-stacks.html"&gt;AWS CloudFormation Nested Stacks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.serverless.com/"&gt;Serverless Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/serverless-plugin-split-stacks"&gt;Serverless Plugin Split Stacks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>tutorial</category>
      <category>cloudformation</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Learn Security Best Practices with Sls-mentor latest Security Package</title>
      <dc:creator>Vincent Zanetta</dc:creator>
      <pubDate>Fri, 08 Dec 2023 13:48:31 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/new-sls-mentor-security-package-41ji</link>
      <guid>https://dev.to/slsbytheodo/new-sls-mentor-security-package-41ji</guid>
      <description>&lt;p&gt;In the realm of cloud computing, serverless has emerged as a popular paradigm, offering a pay-as-you-go model and the ability to scale applications dynamically. However, with such flexibility comes the potential for security vulnerabilities. To address this need, sls-mentor, an open-source tool, has been developed to audit and improve the security of your AWS Serverless applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;New Security Rules for Enhanced Protection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We are excited to announce the release of new security rules for sls-mentor to further enhance the security of your serverless applications. These rules cover IAM role policies, SQS queues, and RDS instances.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IAM Role Policies:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No IAM Role Policy with wildcard Resource:&lt;/strong&gt; Using wildcards in IAM role policies can grant excessive access to resources, making it easier for attackers to exploit. To mitigate this risk, we recommend using specific resources in IAM policies instead of wildcards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;SQS Queues:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Encrypt your SQS queues:&lt;/strong&gt; SQS queues store messages, making them potential targets for unauthorized access. To safeguard your message data, encrypt your SQS queues. SQS supports server-side encryption, ensuring that the data is encrypted on the server before it is stored in the queue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;RDS Instances:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Encrypt your RDS instances:&lt;/strong&gt; RDS databases manage sensitive data, and encrypting them is crucial for protecting this information. sls-mentor checks for unencrypted RDS instances and provides instructions on how to enable encryption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Using sls-mentor to Enhance Security&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In addition to the newly introduced security rules, sls-mentor already includes a comprehensive set of security checks that cover various aspects of AWS Serverless applications. These include checks for insecure IAM role permissions, vulnerable Lambda function configurations, and unencrypted CloudWatch logs. By leveraging these existing checks and the newly added ones, developers can thoroughly assess and strengthen the security posture of their serverless applications.&lt;/p&gt;

&lt;p&gt;To utilize sls-mentor and benefit from its security enhancement capabilities, follow these simple steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run sls-mentor:&lt;/strong&gt; With your AWS credentials configured, run the following command to initiate the security analysis:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  npx sls-mentor@latest &lt;span class="nt"&gt;--report&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Review the security report:&lt;/strong&gt; The sls-mentor tool will generate a comprehensive report in your current directory, named &lt;code&gt;.sls-mentor/index.html&lt;/code&gt;. This report provides insights into the security posture of your serverless applications, highlighting areas for improvement and offering actionable recommendations. &lt;strong&gt;Join the Community and Contribute&lt;/strong&gt; We are always striving to enhance sls-mentor’s capabilities and welcome contributions from the community. If you have serverless or serverful knowledge to share, feel free to join our GitHub repository and contribute to our ongoing development efforts. Your contributions will directly impact the security of serverless applications worldwide.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt; By incorporating sls-mentor into your serverless development workflow, you can proactively identify and address potential security vulnerabilities, ensuring the safety and integrity of your applications in the cloud. Join us in strengthening the security posture of serverless deployments and make your cloud infrastructure more resilient to attacks.&lt;/p&gt;

</description>
      <category>security</category>
      <category>aws</category>
      <category>serverless</category>
    </item>
    <item>
      <title>5 Serverless Best Practices to Become a Skilled Cloud Architect</title>
      <dc:creator>Pierre Chollet</dc:creator>
      <pubDate>Wed, 06 Dec 2023 13:58:11 +0000</pubDate>
      <link>https://dev.to/slsbytheodo/5-serverless-best-practices-to-become-a-skilled-cloud-architect-5dej</link>
      <guid>https://dev.to/slsbytheodo/5-serverless-best-practices-to-become-a-skilled-cloud-architect-5dej</guid>
      <description>&lt;h3&gt;
  
  
  Discover serverless best practices with sls-mentor
&lt;/h3&gt;

&lt;p&gt;In this article, I will go through 5 low cost, high impact serverless best practices that will help you build faster, cheaper, greener, more secure and stable applications on AWS.&lt;/p&gt;

&lt;p&gt;Each best practice is illustrated with an infographic, to make it easy to understand and share with your colleagues. I hope you will enjoy it!&lt;/p&gt;

&lt;p&gt;This compilation is based on &lt;a href="https://www.sls-mentor.dev" rel="noopener noreferrer"&gt;🛡 sls-mentor 🛡&lt;/a&gt;, my free open-source tool that &lt;strong&gt;automatically checks 30 serverless best practices on your AWS serverless projects&lt;/strong&gt; (no matter the framework). Feel free to check it out!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/sls-mentor/sls-mentor" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Find sls-mentor on Github ⭐️&lt;/a&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's connect!
&lt;/h3&gt;

&lt;p&gt;I you want to stay in touch here is my &lt;a href="https://twitter.com/PierreChollet22" rel="noopener noreferrer"&gt;twitter account&lt;/a&gt;. I often post or re-post interesting stuff about AWS and serverless, feel free to follow me!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/PierreChollet22" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Follow me on twitter 🚀&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  1 - When deploying a Lambda function, use a ARM64 architecture instead of a x86_64 architecture
&lt;/h2&gt;

&lt;p&gt;Two Lambda function processors are available, how to choose wisely? 🤔&lt;/p&gt;

&lt;p&gt;Basically, &lt;strong&gt;you should always go with ARM64&lt;/strong&gt; if your code is compatible! 🚀&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.sls-mentor.dev" rel="noopener noreferrer"&gt;🛡 sls-mentor&lt;/a&gt; has a rule that enforces the usage of ARM64 in your Lambda functions so that you never forget 🧠&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Fsls-mentor%2F5-best-practices%2Fassets%2Farm64-vs-x86_64.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Fsls-mentor%2F5-best-practices%2Fassets%2Farm64-vs-x86_64.png" alt="Lambda ARM64 vs x86_64"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2 - Use intelligent tiering on S3 Buckets to reduce storage costs
&lt;/h2&gt;

&lt;p&gt;Which storage class fits the best for my #AWS app?&lt;/p&gt;

&lt;p&gt;The choice is easy: &lt;strong&gt;use Intelligent Tiering!&lt;/strong&gt; It automatically moves files between classes based on their usage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.sls-mentor.dev" rel="noopener noreferrer"&gt;🛡 sls-mentor&lt;/a&gt; automatically check that your buckets have Intelligent Tiering enabled, so that you never forget🧠&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Fsls-mentor%2F5-best-practices%2Fassets%2Fs3-intelligent-tiering.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Fsls-mentor%2F5-best-practices%2Fassets%2Fs3-intelligent-tiering.png" alt="S3 Intelligent Tiering"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3 - Deploy fast Lambda functions with bundles smaller than 5MB
&lt;/h2&gt;

&lt;p&gt;My Lambda function has insane cold starts, what should I do? 😿&lt;/p&gt;

&lt;p&gt;Short answer: &lt;strong&gt;make its bundle smaller!&lt;/strong&gt; On a given runtime, bundle size is the n°1 cause of long cold starts 🐢&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.sls-mentor.dev" rel="noopener noreferrer"&gt;🛡 sls-mentor&lt;/a&gt; lists every Lambda function with a bundle larger than 5MB, try it on your @awscloud account!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Fsls-mentor%2F5-best-practices%2Fassets%2Flambda-bundle-size.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Fsls-mentor%2F5-best-practices%2Fassets%2Flambda-bundle-size.png" alt="Lambda bundle size"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4 - Do not keep your CloudWatch logs forever to reduce storage costs
&lt;/h2&gt;

&lt;p&gt;CloudWatch by @awscloud is known to be expensive 💸&lt;/p&gt;

&lt;p&gt;There is a simple trick to reduce costs: 🚨Do not keep your logs forever🚨&lt;/p&gt;

&lt;p&gt;Storage is cheap, but accumulating it over years quickly gets out of hands 📈&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.sls-mentor.dev" rel="noopener noreferrer"&gt;🛡 sls-mentor&lt;/a&gt; can list Log Groups with infinite retention, try it out 🚀&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Fsls-mentor%2F5-best-practices%2Fassets%2Fcloudwatch-logs-retention.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Fsls-mentor%2F5-best-practices%2Fassets%2Fcloudwatch-logs-retention.png" alt="CloudWatch Logs retention"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5 - Use an Authorizer on all your API Gateway endpoints to secure your API
&lt;/h2&gt;

&lt;p&gt;When deploying an API, &lt;strong&gt;anyone with its URL can access your resources by default&lt;/strong&gt; 😱&lt;/p&gt;

&lt;p&gt;Authorizers allow you to secure your API, by relying on Cognito, IAM, or any custom integration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.sls-mentor.dev" rel="noopener noreferrer"&gt;🛡 sls-mentor&lt;/a&gt; can list all API Gateway endpoints without an Authorizer, try it out 🚀&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Fsls-mentor%2F5-best-practices%2Fassets%2Fapi-gateway-authorizer.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPChol22%2Fkumo-articles%2Fmaster%2Fblog-posts%2Fsls-mentor%2F5-best-practices%2Fassets%2Fapi-gateway-authorizer.png" alt="API Gateway Authorizer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Many more best practices to discover and automate!
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.sls-mentor.dev" rel="noopener noreferrer"&gt;🛡 sls-mentor&lt;/a&gt; is a compilation of 30 serverless best-practices, that are automatically checked on your AWS serverless projects (no matter the framework). It is free and open source, feel free to check it out!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/sls-mentor/sls-mentor" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Find sls-mentor on Github ⭐️&lt;/a&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's connect!
&lt;/h3&gt;

&lt;p&gt;I would really appreciate if you could react and share this article with your friends and colleagues. It will help me a lot to grow my audience. Also, don't forget to subscribe to be updated when the next article comes out!&lt;/p&gt;

&lt;p&gt;I you want to stay in touch here is my &lt;a href="https://twitter.com/PierreChollet22" rel="noopener noreferrer"&gt;twitter account&lt;/a&gt;. I often post or re-post interesting stuff about AWS and serverless, feel free to follow me!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/PierreChollet22" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Follow me on twitter 🚀&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
