<?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: Marko Djakovic</title>
    <description>The latest articles on DEV Community by Marko Djakovic (@imflamboyant).</description>
    <link>https://dev.to/imflamboyant</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F744579%2Fe36b8e07-2ecf-447c-b9a1-9ce51865b599.jpeg</url>
      <title>DEV Community: Marko Djakovic</title>
      <link>https://dev.to/imflamboyant</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/imflamboyant"/>
    <language>en</language>
    <item>
      <title>Add Chat AI Summary Using Amazon Bedrock and HTTP Response Streaming</title>
      <dc:creator>Marko Djakovic</dc:creator>
      <pubDate>Mon, 06 Apr 2026 18:24:23 +0000</pubDate>
      <link>https://dev.to/aws-builders/add-chat-ai-summary-using-amazon-bedrock-and-http-response-streaming-522h</link>
      <guid>https://dev.to/aws-builders/add-chat-ai-summary-using-amazon-bedrock-and-http-response-streaming-522h</guid>
      <description>&lt;p&gt;Ever since I started writing what turned out to be a series of articles on how to build real-time chat apps natively on AWS, I had a huge backlog of ideas for extending the basic chat, which included adding authentication &amp;amp; authorization, full-text search, threads, reactions, and many other things that make any chat system production grade and up to par with what users expect these days. Given how AI has entered every pore of the industry, extending my small chat project had to go in this direction sooner or later. It all started with &lt;a href="https://marko.dj/posts/2024-12-23-build-cloud-native-serverless-chat-on-aws/" rel="noopener noreferrer"&gt;creating a solution that (ab)uses IoT Core&lt;/a&gt;, which worked exceptionally well - maybe even too well, as AWS ended up designing AppSync Events in a similar fashion. Read more on what I mean in my blog post about &lt;a href="https://marko.dj/posts/2025-01-20-serverless-chat-on-aws-with-appsync-events/" rel="noopener noreferrer"&gt;Serverless Chat on AWS with AppSync Events&lt;/a&gt;. Shortly after the initial release, AWS pushed out the promised improvements for AppSync Events, which I explore in the following &lt;a href="https://marko.dj/posts/2025-11-02-how-to-better-serverless-chat-on-aws-over-websockets/" rel="noopener noreferrer"&gt;blog post about leveraging WebSockets with it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To build on top of existing chat solution, this time we won't touch the main infrastructure that powers the chat but rather extend the group chat capabilities by adding a dedicated AI summary endpoint. This is one of the most common AI use cases, and apps like Viber or Slack already have it. Most of us can relate to the pain of having to go through numerous Slack threads with dozens of messages each - so I'd say this feature is more than only nice to have at this point. Let's see how we can do it using Amazon Bedrock's ConverseStreamCommand, and leveraging HTTP response streaming capability of API Gateway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;As I mentioned, we won't be touching the core infrastructure of the chat solution which is AppSync and the whole WebSockets part, but rather extend the system with a dedicated endpoint to create the AI summary of chat messages. So, essentially what we're talking about here is shown in the following diagram:&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%2Fmso63oh4hl7k54y7p52j.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%2Fmso63oh4hl7k54y7p52j.png" alt="Architecture" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;New, and the most interesting addition here is Amazon Bedrock. Also, since API Gateway supports HTTP response streaming, we'll make sure to leverage that to get the fastest possible response when invoking AI summary endpoint.&lt;/p&gt;

&lt;p&gt;If we zoom back out, the whole solution looks something like:&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%2Ffaxasc1r5ewgitah01m9.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%2Ffaxasc1r5ewgitah01m9.png" alt="Full architecture" width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So just to be clear - this is what will get deployed if you decide to run this in your own account. Let's break down how the two new key pieces fit into the mix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Amazon Bedrock
&lt;/h3&gt;

&lt;p&gt;I won't waste words on this since it is quite familiar, but in short - Bedrock is a fully managed, serverless AWS service for building generative AI applications. It provides a single API access for wide variety of foundational models from leading AI companies such as Anthropic, Meta, and Amazon itself. For this particular example I will rely on one of the smallest Amazon models - &lt;code&gt;amazon.nova-micro-v1:0&lt;/code&gt;. It is more than enough for showcasing the idea, but feel free to play with others according to your liking.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Small caveat: when specifying the model, it is not enough to put only the name of the model, but you need to include also the region prefix of where your app will run. For example in my case it was &lt;code&gt;eu.amazon.nova-micro-v1:0&lt;/code&gt;. Worth knowing just so you don't get confused when you see it in the code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A dedicated Lambda function for AI summary will first fetch group chat messages from the database, and then create a prompt for Bedrock to create a short summary. It uses &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/bedrock-runtime/command/ConverseStreamCommand/" rel="noopener noreferrer"&gt;ConverseStreamCommand&lt;/a&gt; by &lt;code&gt;BedrockRuntimeClient&lt;/code&gt; to send the prompt to Bedrock and get the stream response. To make the response smoothly stream into Lambda function's response, there is a small caveat compared to how you would normally define the handler in TypeScript. One is defining the handler as:&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;awslambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;streamifyResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;awslambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HttpResponseStream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and handling the streaming response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;httpStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;awslambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HttpResponseStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/plain; charset=utf-8&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;Access-Control-Allow-Origin&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;*&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;...and then the &lt;code&gt;httpStream&lt;/code&gt; will be used to write the stream response from Bedrock into it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: for simplicity I have set a limit of 50 messages to fetch for the summary, however it is configurable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  HTTP Response Streaming with API Gateway
&lt;/h3&gt;

&lt;p&gt;Finally, the last piece to get the streaming response to the client is required to be configured on API Gateway. This capability was &lt;a href="https://aws.amazon.com/blogs/compute/building-responsive-apis-with-amazon-api-gateway-response-streaming/" rel="noopener noreferrer"&gt;introduced in November 2025&lt;/a&gt; and has CDK support from version 2.227.0 via &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.LambdaIntegrationOptions.html#responsetransfermode" rel="noopener noreferrer"&gt;&lt;code&gt;responseTransferMode&lt;/code&gt; property&lt;/a&gt;. Setting it to &lt;code&gt;ResponseTransferMode.STREAM&lt;/code&gt; on the Lambda integration resource will enable streaming:&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;getSummaryResource&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;GET&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="nc"&gt;LambdaIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getSummaryLambda&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;responseTransferMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ResponseTransferMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STREAM&lt;/span&gt;&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;To showcase how the finished feature works, I've created a short video. I prefilled the group chat with test messages to simulate a conversation between team of engineers working on a production deployment. The AI summary will summarize their agreements and key points they discussed about the deployment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://marko.dj/posts/2026-04-05-add-ai-summary-to-appsync-chat-aws-bedrock#demo" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Watch the demo video&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;In the end, building an AI summary today isn’t something groundbreaking, and to be honest that might be the point. What used to feel like cutting-edge quickly became the standard what users expect in these types of applications. Amazon Bedrock doing the heavy lifting on the model side makes the overall path from idea to working solution really smooth. I encourage you to try it yourself, check out the code and follow the instructions to deploy to your own AWS account:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/imflamboyant/serverless-aws-chat/tree/main/chat-appsync-events-websocket" rel="noopener noreferrer"&gt;https://github.com/imflamboyant/serverless-aws-chat/tree/main/chat-appsync-events-websocket&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;💸 Note: as always, be aware that usage might incur real costs, though relatively small for this example. If you have free tier or credits, just make sure that Bedrock is covered.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and stay tuned, because next I’ll take this further into agentic chat and explore what’s possible with Strands.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>ai</category>
      <category>bedrock</category>
      <category>appsync</category>
    </item>
    <item>
      <title>How To: Better Serverless Chat on AWS over WebSockets</title>
      <dc:creator>Marko Djakovic</dc:creator>
      <pubDate>Mon, 03 Nov 2025 19:48:26 +0000</pubDate>
      <link>https://dev.to/aws-builders/how-to-better-serverless-chat-on-aws-over-websockets-5c1e</link>
      <guid>https://dev.to/aws-builders/how-to-better-serverless-chat-on-aws-over-websockets-5c1e</guid>
      <description>&lt;p&gt;When I first wrote about building a serverless chat on AWS using AppSync Events, it was lacking two key capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;two-way communication over WebSockets, and&lt;/li&gt;
&lt;li&gt;message persistence on publish&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since then, AppSync Events API received some improvements, including  these two capabilities that it initially lacked. Having tested this first hand on a simple example, my aim with this post is to share impressions of what now seems to be a way more complete service offering. Spoiler alert - the impressions are rather good, and make sure to stick around to the end as code example is included!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: this post is also published on my personal website: &lt;a href="https://marko.dj/posts/2025-11-02-how-to-better-serverless-chat-on-aws-over-websockets/" rel="noopener noreferrer"&gt;marko.dj&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Before
&lt;/h2&gt;

&lt;p&gt;There were some workarounds proposed at the time by AWS themselves, but mine was maybe a bit more robust, coming with the complexity trade-off. You can read the previous article &lt;a href="https://marko.dj/posts/2025-01-20-serverless-chat-on-aws-with-appsync-events/" rel="noopener noreferrer"&gt;here&lt;/a&gt;, but this is how the architecture looks like:&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%2Ff39wk4jusewj5o72dkcf.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%2Ff39wk4jusewj5o72dkcf.png" alt="previous architecture" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I made a clear separation - use AppSync Events only for subscribing, while I had a separate API for publishing. Messages were stored and streamed to AppSync, making their way to all subscribers. This isn't ideal, but ensures consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  After
&lt;/h2&gt;

&lt;p&gt;Given the new features AWS introduced to the service, now it's possible to achieve even better performance with even simpler architecture. The new solution looks like in the below diagram:&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%2Fefvazfke00j6xirbue9l.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%2Fefvazfke00j6xirbue9l.png" alt=" " width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With these improvements we remove several elements of the previous solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No DynamoDB Streams&lt;/li&gt;
&lt;li&gt;No EventBridge Pipes&lt;/li&gt;
&lt;li&gt;No separate API endpoint for publishing messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The new improvements allow sending and receiving messages completely over WebSockets natively using AppSync Events realtime endpoint, so there is no need for workarounds anymore. We still keep the GET API endpoint for fetching existing messages, so no change regarding that. Note that this can be simplified even further by using only Lambda Function URL, but we'll be keeping the approach with API Gateway. Now, let's address what's needed to make the new solution work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it work
&lt;/h2&gt;

&lt;p&gt;As with both previous articles we will be using AWS CDK for the project. Perhaps the most interesting part is creating the AppSync Events API, configuring the channel namespace, and the publishing handler function. All of that is set up in this piece of CDK code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AppSync Event API, channel namespace, and handler&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventApi&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;EventApi&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;ChatAppSyncEventsApi&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;apiName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ChatAppSyncEventApiWs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Add AppSync DynamoDB data source &amp;amp; add needed permission&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appSyncDynamoDbDataSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;eventApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addDynamoDbDataSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ddbsource&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grantReadWriteData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appSyncDynamoDbDataSource&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// create namespace with datasource and handler for onPublish&lt;/span&gt;
&lt;span class="nx"&gt;eventApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addChannelNamespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;serverlesschat&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;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AppSyncCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;../src/appsyncjs/serverlesschat-handler.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="na"&gt;publishHandlerConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appSyncDynamoDbDataSource&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;Simple enough, right? Let's break it down a bit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Channel namespaces
&lt;/h3&gt;

&lt;p&gt;Channel namespaces are something that's different than when we built this solution with IoT Core Topics. Topics are pretty loose and you can define your own topics without prior declaration or creation. While AppSync requires explicit declaration of the namespace first, the channels you can create within it are still very flexible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Event handlers &amp;amp; data sources
&lt;/h3&gt;

&lt;p&gt;This is the real breakthrough feature addition - more complex handlers and the ability to interact with data sources. This is what enables us to ditch the whole workaround from the previous solution that was using a separate REST API for storing messages to DynamoDB, and then streaming them to EventBridge Pipes to get them to subscribers. In the new solution, messages are published directly via WebSockets, and stored into DynamoDB using the onPublish event handler - no need for any additional streaming as subscribers get messages directly.&lt;/p&gt;

&lt;p&gt;Two types of handlers exist - onSubscribe and onPublish, and can be used for various things, such as transformations, persistence to a data source, or even authorization. They are written in JS, using the &lt;code&gt;APPSYNC_JS&lt;/code&gt; runtime, which might be familiar to users of AppSync already. The scope of the article is not to go deep dive into this topic, even though it very well could - for those interested in more details feel free to check out &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_appsync-readme.html#events" rel="noopener noreferrer"&gt;official AWS CDK docs on this&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeing it in action
&lt;/h2&gt;

&lt;p&gt;To test the solution yourself, checkout the code and follow the README at:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/imflamboyant/serverless-aws-chat/tree/main/chat-appsync-events-websocket" rel="noopener noreferrer"&gt;https://github.com/imflamboyant/serverless-aws-chat/tree/main/chat-appsync-events-websocket&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thanks for reading! Have some ideas how you would extend the solution? Do let me know in the comments!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>appsync</category>
      <category>chat</category>
    </item>
    <item>
      <title>Serverless Chat on AWS with AppSync Events</title>
      <dc:creator>Marko Djakovic</dc:creator>
      <pubDate>Mon, 20 Jan 2025 22:26:03 +0000</pubDate>
      <link>https://dev.to/aws-builders/serverless-chat-on-aws-with-appsync-events-37g4</link>
      <guid>https://dev.to/aws-builders/serverless-chat-on-aws-with-appsync-events-37g4</guid>
      <description>&lt;p&gt;In my previous article I've shown how easy it can be to create a &lt;a href="https://marko.dj/posts/2024-12-23-build-cloud-native-serverless-chat-on-aws/" rel="noopener noreferrer"&gt;real-time chat application using AWS IoT Core&lt;/a&gt;. As I mention there, I'd like to explore implementing the same solution using a recently announced AppSync Event API. In this article, I will do just that. Along the way, we will discuss the similarities and the differences between the two approaches, while covering the basics of AppSync Events, architecture choices, caveats and more. Same like last time, I will include a CDK project so you can play with it on your own using a simple client to test it live.&lt;/p&gt;

&lt;p&gt;Side note: this post is also published on my personal website - &lt;a href="https://marko.dj/posts/2025-01-20-serverless-chat-on-aws-with-appsync-events/" rel="noopener noreferrer"&gt;marko.dj&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  AppSync Events
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-welcome.html" rel="noopener noreferrer"&gt;AppSync Events&lt;/a&gt; was announced in October 2024 and it really sounds exciting. I won't bother you much with repeating what is already said about the service itself, so in a nutshell, citing the AWS guide:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AWS AppSync Events lets you create secure and performant serverless WebSocket APIs that can broadcast real-time event data to millions of subscribers, without you having to manage connections or resource scaling.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I immediately wanted to try it out, but I quickly realized it is still "young" and lacks a lot of features that would be necessary for a lot of use cases. For example, currently there is no direct way to store the messages from channels anywhere. I expected that you'd be able to at least trigger a Lambda function and do something custom with the messages, like storing them to DynamoDB. Message processing is possible via &lt;a href="https://docs.aws.amazon.com/appsync/latest/eventapi/channel-namespace-handlers.html" rel="noopener noreferrer"&gt;namespace handlers&lt;/a&gt; by using &lt;code&gt;onPublish&lt;/code&gt; and &lt;code&gt;onSubscribe&lt;/code&gt; handlers, however since they run on AppSync's JavaScript runtime there is no possibility to do much more than on the fly processing or filtering of the payload.&lt;/p&gt;

&lt;p&gt;In this article, I'll mainly focus on working around the lack of persistence capabilities. A great &lt;a href="https://youtu.be/mc27pPLDFAw" rel="noopener noreferrer"&gt;re:Invent 2024 session&lt;/a&gt; contains a detailed presentation of the service, addressing the drawbacks and suggesting alternative solutions. The session is very informative and also addresses the roadmap, according to which all the missing features (and more) are planned to be delivered. Now, I would like to dig in the event persistence possibilities with AppSync Events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Events Persistence Issue
&lt;/h3&gt;

&lt;p&gt;In the re:Invent session shared above, the presenter addresses the event persistence issue at &lt;a href="https://www.youtube.com/watch?v=mc27pPLDFAw&amp;amp;t=2675s" rel="noopener noreferrer"&gt;around 44:35&lt;/a&gt;, presenting workarounds as "Advanced patterns". The workaround is basically having an API next to the AppSync Event API that will be used to persist events separately. This can look something like:&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%2Fpw4up3x0866nu27rpft4.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%2Fpw4up3x0866nu27rpft4.png" alt="Proposed architecture" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since the side API would be there anyway for fetching previous messages, adding one more endpoint for storing messages is not a big deal. Relatively straightforward, it will work, but there's a catch. This way the client is burdened with making two requests when publishing messages - one towards the AppSync Event API to publish it to subscribers, and one to the side API that will actually persist the event to the database. If there's an issue with the API persisting the events, there will be inconsistencies between what is persisted and what the subscribers get. Since the client has to send events to two destinations and both need to succeed, handling retries in case of failures is adding complexity as well. I would rather call this an anti-pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Look at an Alternative Solution
&lt;/h3&gt;

&lt;p&gt;I would like to present an alternative approach to this challenge. Besides the WebSockets endpoint, AppSync Event API has an HTTP endpoint which was highlighted as a neat feature to integrate not only frontend clients, but also backends that could publish events to it. To achieve consistency and a more robust solution, leveraging DynamoDB Streams and EventBridge Pipes can be the way to go. This way the client makes only one request when sending messages, and the subscribers always get the messages that are actually persisted. Effectively, this means that the client does not use the HTTP endpoint of AppSync Events at all, but relies on the side API for sending messages, and only listens to incoming messages via the realtime endpoint. The diagram below shows this architecture more clearly.&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%2Fgc6tfbsxip5sjvr1ss55.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%2Fgc6tfbsxip5sjvr1ss55.png" alt="Alternative architecture" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is possible by leveraging some of many EventBridge capabilities. Namely, DynamoDB Streams are activated on the messages table, streaming each new message through an EventBridge Pipe to an &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-api-destinations.html" rel="noopener noreferrer"&gt;API Destination&lt;/a&gt;, connected to the actual HTTP endpoint of AppSync Events.&lt;/p&gt;

&lt;p&gt;Since I'm a big fan of CDK, and like to be on the bleeding-edge, I am relying on a few alpha modules from CDK: &lt;a href="https://constructs.dev/packages/@aws-cdk/aws-pipes-alpha/v/2.175.1-alpha.0?lang=typescript" rel="noopener noreferrer"&gt;&lt;code&gt;@aws-cdk/aws-pipes-alpha&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://constructs.dev/packages/@aws-cdk/aws-pipes-sources-alpha/v/2.175.1-alpha.0?lang=typescript" rel="noopener noreferrer"&gt;&lt;code&gt;@aws-cdk/aws-pipes-sources-alpha&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://constructs.dev/packages/@aws-cdk/aws-pipes-targets-alpha/v/2.175.1-alpha.0?lang=typescript" rel="noopener noreferrer"&gt;&lt;code&gt;@aws-cdk/aws-pipes-targets-alpha&lt;/code&gt;&lt;/a&gt; and this is what it takes to connect with DynamoDB Streams:&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;// EventBridge Pipe DynamoDB Stream Source&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamoDbStreamSource&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;DynamoDBSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;startingPosition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBStartingPosition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LATEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// EventBridge API Destination&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appSyncEventsApiDestination&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;ApiDestination&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;AppSyncEventsApiDestination&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;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appSyncEventsApiConnection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appSyncEventsApiEndpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;httpMethod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// EventBridge Pipe with API Destination Target &amp;amp; Input Transformation&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipe&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;Pipe&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;EBPipe&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;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamoDbStreamSource&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApiDestinationTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appSyncEventsApiDestination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;inputTransformation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InputTransformation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;serverlesschat/channels/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;$.dynamodb.NewImage.channel.S&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&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;channel&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;$.dynamodb.NewImage.channel.S&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;timestamp&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;$.dynamodb.NewImage.timestamp.S&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;$.dynamodb.NewImage.username.S&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;message&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;$.dynamodb.NewImage.message.S&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="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="c1"&gt;// api key must be sent with each message&lt;/span&gt;
        &lt;span class="na"&gt;headerParameters&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;X-Api-Key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appSyncEventsApiKey&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;Input transformation is performed to accommodate DynamoDB Streams to a format required by AppSync Events endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"events"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;events&lt;/code&gt; is an array of strings, hence json messages are stringified during transformation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; activating CloudWatch logs for the Pipe with &lt;em&gt;Log execution data&lt;/em&gt; turned on can save you a bunch of time troubleshooting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Trade-offs
&lt;/h4&gt;

&lt;p&gt;As with any architecture decision, there are trade-offs, so let's just address them with regard to adding DynamoDB Streams and an EventBridge Pipe. To improve consistency and simplify client communication, the speed of message delivery is reduced. What does this mean? There is a slight latency noticed in message delivery to subscribed clients. This is not surprising since the message goes&lt;br&gt;
through the API Gateway and a Lambda function, to a DynamoDB table and from there it is streamed to the AppSync Event API  via an EventBridge Pipe, which is basically another HTTP call. So, a lot of stuff happening in a few dozen lines of code. I just can't help but notice that the IoT Core solution is slightly snappier overall. This will of course be solved when AppSync Events gets two-way WebSocket communication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authorization
&lt;/h3&gt;

&lt;p&gt;A few words on authorization. The easiest way to get started with AppSync Events is to use API Key auth, and that's exactly how this project is configured. Other modes are supported as well, pretty standard for AWS: Cognito User Pool, IAM, OIDC, and custom AWS Lambda. These choices offer great flexibility for various authorization needs, especially given the fact that it can be configured per namespace.&lt;/p&gt;

&lt;h4&gt;
  
  
  Authorization with EventBridge API Destination
&lt;/h4&gt;

&lt;p&gt;As seen in the above CDK code snipped, an API Destination is configured with a Connection to support authorization when sending messages towards the AppSync Event HTTP API endpoint. Additionally, the destination target configured in EventBridge Pipe is required to have &lt;code&gt;X-Api-Key&lt;/code&gt; header sent with each request for the messages to get through. What I noticed is that once this header is missing, or the value is incorrect - the Connection becomes &lt;code&gt;Deauthorized&lt;/code&gt; automatically and the whole Pipe stops working as a result. I haven't found a way to fix that, but just to destroy and deploy the stack again. I am not sure what it takes to authorize the API Destination again, but it was rather inconvenient from developer experience standpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo &amp;amp; Code
&lt;/h2&gt;

&lt;p&gt;Similar as in the last article, I created a simple HTML client powered by some JavaScript to test &amp;amp; demo this solution. Generally, examples on configuring the client use Amplify, which is a neat library when building with SPA frameworks. I opted for a plain HTML page which uses native browser &lt;code&gt;fetch&lt;/code&gt; and &lt;code&gt;websocket&lt;/code&gt; APIs, without any external dependencies. You don't have to worry about the technicalities if you just want to run the example yourself, however if you'd like to dive deeper on how to connect to AppSync Events without Amplify, feel free to consult &lt;a href="https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-websocket-protocol.html" rel="noopener noreferrer"&gt;Understanding the Event API WebSocket protocol&lt;/a&gt; AWS guide and my code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/imflamboyant/serverless-aws-chat" rel="noopener noreferrer"&gt;https://github.com/imflamboyant/serverless-aws-chat&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AWS Deployment and usage instructions are provided in the repository's README file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;In this article I have shared my experience in building a familiar real-time chat solution, this time with a brand-new AWS offering - AppSync Events. It does feel similar to IoT Core, mostly due to the way subscriptions are organized with namespaces/channels, but I must admit it gives a more "native" developer experience. &lt;/p&gt;

&lt;p&gt;The roadmap shared at re:Invent 2024 sounds promising, stating that we can expect features like two-way websocket communication, data persistence options, and Lambda handlers, among others. When all that gets released, I truly believe this will be one of the most important AWS services for building modern applications.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>appsync</category>
      <category>eventdriven</category>
    </item>
    <item>
      <title>Building a Cloud Native Serverless Chat On AWS</title>
      <dc:creator>Marko Djakovic</dc:creator>
      <pubDate>Mon, 23 Dec 2024 11:43:56 +0000</pubDate>
      <link>https://dev.to/aws-builders/building-a-cloud-native-serverless-chat-on-aws-4enf</link>
      <guid>https://dev.to/aws-builders/building-a-cloud-native-serverless-chat-on-aws-4enf</guid>
      <description>&lt;p&gt;I've wanted to write about this for over a year probably, but I kept putting it off for various reasons. I built a real-time chat on AWS by (ab)using the IoT Core service, and I've been eager to share my experience. If you'd prefer, you can read this post also on my personal website - &lt;a href="https://marko.dj/posts/2024-12-23-build-cloud-native-serverless-chat-on-aws/" rel="noopener noreferrer"&gt;marko.dj&lt;/a&gt;. Now, back to the topic. Recently, &lt;a href="https://aws.amazon.com/about-aws/whats-new/2024/10/aws-appsync-websocket-apis-web-mobile-experiences/" rel="noopener noreferrer"&gt;AWS announced AppSync Events&lt;/a&gt; and my immediate thought was, "wow, I can replace the IoT Core solution with this!"&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;...a new solution for building secure and performant serverless WebSocket APIs to power real-time web and mobile experiences at any scale.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not so fast... It turns out the initial version of AppSync Events has some significant limitations. For starters, there's no way to persist data by integrating with other AWS services. Persistence, along with many other features, is said to be on the roadmap for next year. I'm eagerly awaiting these updates, but until then, let me walk you through how to build real-time apps on AWS using this IoT Core based approach. It's a surprisingly straightforward method that leverages AWS serverless capabilities effectively.&lt;/p&gt;

&lt;p&gt;I'll present a cloud architecture for a real-time chat application, accompanied by useful code snippets and infrastructure written in CDK, ready to be deployed and tested in your AWS environment. Along the way, I'll explain the design choices, discuss the options available, and also highlight some caveats of this approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;We are building a simple group chat that will have some pre-defined channels that users can subscribe to and send and receive messages. The application will use IoT Core Topics as the backbone for real-time messaging, with a few additional AWS serverless services to support things like data persistence and authorization. Apart from the real-time segment, the app will have a supporting REST API to fetch existing messages per channel.&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%2Fkdi5fd396t55xp1bpct8.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%2Fkdi5fd396t55xp1bpct8.png" alt="Serverless Chat Architecture" width="534" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This architecture allows us to publish messages and subscribe to topics directly on the IoT Core service. Published messages will automatically be stored in a DynamoDB table and subscribed clients will receive them in real time. As you can see on the diagram, there is also a Lambda authorizer which is needed to allow actions on IoT Core. For fetching previous messages from the database upon connecting, clients will use the get messages API that queries the DynamoDB table fetching messages from a requested channel. I won't go into technicalities of this API as it's not the main focus of the article, and it is a pretty standard serverless API.&lt;/p&gt;

&lt;p&gt;Chat message payload looks quite simple as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"general"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2018-11-07T00:25:00.073UTC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"johndoe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hello world"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before diving into the implementation, let's cover some basic concepts important for understanding the solution.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/what-is-aws-iot.html" rel="noopener noreferrer"&gt;AWS IoT Core&lt;/a&gt; is a pretty powerful and feature-rich service, and the depths of its possibilities are beyond the scope of this article. What's important for us at the moment is to understand that it's essentially an MQTT broker which enables real time communication between connected applications, using Topics, Rules and Actions. In all AWS accounts there is an endpoint in each region that can be used to connect to the broker, and subscribe and publish messages to topics.&lt;/p&gt;

&lt;h4&gt;
  
  
  Topics, Rules &amp;amp; Actions
&lt;/h4&gt;

&lt;p&gt;Creating a &lt;em&gt;Topic&lt;/em&gt; isn't necessary as it is just a logical resource specified as a plain string at runtime. Usually, topic names are divided by slashes into logical parts, for example &lt;code&gt;chat/general&lt;/code&gt;. One of the standout features of IoT Core are &lt;em&gt;Rules&lt;/em&gt;, which allows you to filter, transform, and route messages to other AWS services through &lt;em&gt;Actions&lt;/em&gt;. For instance, you can set up a rule to listen for messages published to a topic, and then trigger an action to store the message in DynamoDB or invoke a Lambda function. Or do a plethora of other things listed under &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-rule-actions.html" rel="noopener noreferrer"&gt;AWS IoT rule actions&lt;/a&gt;. I believe this service is often overlooked partly because of its name, and partly because of its apparent complexity, but it can definitely be effective outside of IoT realm. Let's suppose that our application publishes messages to &lt;code&gt;chat/general&lt;/code&gt; topic. A rule in CDK can be creatred to automatically store them in a DynamoDB table:&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;new&lt;/span&gt; &lt;span class="nc"&gt;TopicRule&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;SendMessagesToDynamoDB&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;topicRuleName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chat_ddb_rule&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IotSql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromStringAsVer20160323&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM 'chat/general'&lt;/span&gt;&lt;span class="dl"&gt;"&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DynamoDBv2PutItemAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tableName&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 code snippet above creates a topic rule, which uses an SQL-like syntax for filtering data from the topic, and defines a DynamoDB PutItem action. The result of the action is a message being stored in the given DynamoDB table. One of the conditions is to have the table key names match the field names in the message payload. If not, the SQL statement could also transform the field names, for example &lt;code&gt;SELECT username AS userId&lt;/code&gt;, in case the table key is &lt;code&gt;userId&lt;/code&gt;. To go in depths of this very powerful feature, full spec is available at &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-sql-reference.html" rel="noopener noreferrer"&gt;AWS IoT SQL reference&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Authorization
&lt;/h4&gt;

&lt;p&gt;The IoT Core regional endpoint is readily available, and after setting up a topic rule, you might expect to be ready to start publishing messages. Not quite yet. By default, no clients are permitted to connect to the broker. IoT Core provides robust and flexible authorization methods to manage access to its resources. In this example, we use a custom Lambda authorizer, allowing you to implement any custom logic to secure your application.  The only condition is that authorizer Lambda's response must include a valid policy object. While there are numerous options for authorization, this article focuses on a simplified approach. For a deeper dive into the available options, refer to the &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-authorization.html" rel="noopener noreferrer"&gt;IoT Core Authorization Guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To keep things straightforward, this example uses a highly permissive policy that grants full access to the client application by default. The goal is to demonstrate real-time communication and help you quickly test the setup. However, this permissive policy should never be used in a production environment. The Lambda authorizer's response is an &lt;code&gt;IoTCustomAuthorizerResult&lt;/code&gt; object, which contains the following policy document:&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="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2012-10-17&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iot:Connect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`arn:aws:iot:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:client/User*`&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iot:Subscribe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`arn:aws:iot:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:topicfilter/serverlesschat/channels/*`&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iot:Receive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iot:Publish&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`arn:aws:iot:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:topic/serverlesschat/channels/*`&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;The policy document consists of three statements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;allowing a clients with ids starting with &lt;code&gt;User*&lt;/code&gt; to connect to the broker&lt;/li&gt;
&lt;li&gt;allowing subscriptions to all topics under &lt;code&gt;serverlesschat/channels/*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;allowing publishing and receiving of messages on the above topics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Connecting by using an MQTT client with an appropriate client id is then quite simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;mqtt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;brokerUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User123&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;h5&gt;
  
  
  Caveats
&lt;/h5&gt;

&lt;p&gt;There are some caveats regarding the authorizer that proved they could be tricky while developing this solution, so I'd like to highlight them specifically.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caveat 1:&lt;/strong&gt; Granting the IoT Core service permission to invoke the Lambda authorizer function is essential for it to work. In CDK it can be as easy as:&lt;/p&gt;


&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;authLambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grantInvoke&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;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;iot.amazonaws.com&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;/blockquote&gt;

&lt;p&gt;This small detail can be a real pain to troubleshoot, because there are no visible errors or logs, the clients just get rejected. So make sure it's set in case you face connection issues.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caveat 2:&lt;/strong&gt; As mentioned earlier, the authorizer response should be of &lt;code&gt;IoTCustomAuthorizerResult&lt;/code&gt; type. However, for an unknown reason it didn't work, the policy just wasn't applied. Only when I changed the response to a plain string, and stringified the result did the policy actually apply. Refer to this piece of code in &lt;code&gt;src/authorizer-handler.ts&lt;/code&gt;.&lt;/p&gt;


&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&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;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;I am still not quite sure why, but I remember having similar issues with this in Java as well.&lt;/p&gt;

&lt;p&gt;If all this so far feels a bit blurry, don’t worry! The complete code for the authorizer, along with the CDK setup and an example client, is available in the GitHub repository linked in the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show Me the Code
&lt;/h2&gt;

&lt;p&gt;To demonstrate the chat functionality, I’ve created a simple HTML client powered by some JavaScript. This basic client connects to the IoT Core endpoint, enables publishing and subscribing to channels, and retrieves any persisted messages from the REST API for the selected channel. Each client instance gets a random username in the format of &lt;code&gt;Userxyz&lt;/code&gt;, where &lt;code&gt;xyz&lt;/code&gt; is a random three-digit number. You can find it, along with the CDK code, in the GitHub repository below. Clone it, deploy it to your AWS account, and start testing right away.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/imflamboyant/serverless-aws-chat" rel="noopener noreferrer"&gt;https://github.com/imflamboyant/serverless-aws-chat&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Deployment and usage instructions are provided in the repository's README file.&lt;/p&gt;

&lt;p&gt;Of course, you're not limited to this example. You can create your own clients using any technology, such as SPA frameworks or mobile applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;In this article I’ve demonstrated how easy it can be to create a real-time chat application on AWS. I have not covered pricing, but given the cheap combined costs of all the involved AWS services, you can process hundreds of thousands of messages before this solution costs you a few dollars.&lt;/p&gt;

&lt;p&gt;What else I like about it is that the extension possibilities are huge. You could extend the API to also manage users and channels, integrate the authorizer with your preferred Identity Provider using JWKS and validate JWTs, or enhance security even more by adjusting the authorizer to check user permissions before allowing subscriptions to specific channels. The chat itself could also be enriched with features like message reactions, threads, and image sharing, or even GenAI capabilities by integrating with Bedrock.&lt;/p&gt;

&lt;p&gt;This architecture lays a solid foundation not only for chat systems but for any real-time communication features you might envision.&lt;/p&gt;

&lt;p&gt;In the next article, I’ll migrate this solution to AppSync Events, explore options to handle the lack of data persistence, and compare its features with those of IoT Core.&lt;/p&gt;

&lt;p&gt;Thank you for reading, and stay tuned for the next article!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>iotcore</category>
    </item>
    <item>
      <title>Power Chat Messages Search with DynamoDB &amp; Amazon OpenSearch</title>
      <dc:creator>Marko Djakovic</dc:creator>
      <pubDate>Mon, 29 Jan 2024 09:08:18 +0000</pubDate>
      <link>https://dev.to/aws-builders/power-chat-messages-search-with-dynamodb-amazon-opensearch-4ffp</link>
      <guid>https://dev.to/aws-builders/power-chat-messages-search-with-dynamodb-amazon-opensearch-4ffp</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;We have built a group chat application that stores chat messages in Amazon DynamoDB and it is running for a few months now. As the application grew, users wanted to be able to search through historical chat messages. To enable this, we will integrate DynamoDB with Amazon OpenSearch Service for full-text search capabilities. There are multiple ways of dealing with this, but I would like to experiment with something new. During the 2023 re:Invent AWS announced &lt;a href="https://aws.amazon.com/blogs/aws/amazon-dynamodb-zero-etl-integration-with-amazon-opensearch-service-is-now-generally-available/" rel="noopener noreferrer"&gt;general availability of DynamoDB zero-ETL integration with Amazon OpenSearch service&lt;/a&gt;. In this article I will give it try on our group chat full-text search use case and share my insights and experience.&lt;/p&gt;

&lt;p&gt;Side note: the post is also published on my personal website - &lt;a href="https://marko.dj/posts/2024-01-25-power-chat-messages-search-dynamodb-opensearch/" rel="noopener noreferrer"&gt;marko.dj&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current situation
&lt;/h2&gt;

&lt;p&gt;Our application already leverages DynamoDB for fast and scalable storage and retrieval of chat messages via an API. The API consists of an API Gateway and a few Lambda functions that interact with a DynamoDB table. That looks something like in the diagram below.&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%2F10u2c5li84widmyfad81.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%2F10u2c5li84widmyfad81.png" alt="current serverless architecture" width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the already standard, boring setup for a Serverless API on AWS 🙃 DynamoDB is not well suited for the full-text search use case. Let's see what needs to be added to achieve this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bridge the gap towards full-text search
&lt;/h2&gt;

&lt;p&gt;To facilitate the full-text search over chat messages we will add Amazon OpenSearch to our architecture. Now, how do we approach syncing the messages data from DynamoDB to OpenSearch? The first thing that comes to mind it of course enabling DynamoDB Streams and having a Lambda function handle them and send the data to OpenSearch. However, we won't do that. We will leverage the zero-ETL integration between the two services. So, our architecture changes to something like:&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%2Fwytn2youa1v6f8j2thi9.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%2Fwytn2youa1v6f8j2thi9.png" alt="architecture added open search" width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up Amazon OpenSearch
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/opensearch-service/" rel="noopener noreferrer"&gt;Amazon OpenSearch&lt;/a&gt; is a managed service and that means the underlying infrastructure is handled for us. We can set up an OpenSearch Domain, which means we specify the instance size and count, and it will be up and running after a short while. The second choice is to use the &lt;a href="https://aws.amazon.com/opensearch-service/features/serverless/" rel="noopener noreferrer"&gt;Serverless option&lt;/a&gt;, which is out of scope for this article. The important thing is that both options support zero-ETL sync from DynamoDB.&lt;/p&gt;

&lt;p&gt;For this example, I provisioned a cluster with only 1 node of type t3.small.search. This is of course not production grade, but more than enough for this small PoC.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data ingestion to OpenSearch
&lt;/h3&gt;

&lt;p&gt;Having the OpenSearch cluster up and running is a great first step, but now we need to achieve two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Import all existing messages from DynamoDB&lt;/li&gt;
&lt;li&gt;Set up constant sync of all future messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Luckily, &lt;a href="https://docs.aws.amazon.com/opensearch-service/latest/developerguide/ingestion.html" rel="noopener noreferrer"&gt;Amazon OpenSearch Ingestion&lt;/a&gt; pipeline can take care of both. How this works is the Pipeline initially exports all the table data to an existing S3 bucket of your choice, imports the data from there into OpenSearch, and then continues streaming all new changes in the table towards OpenSearch.&lt;/p&gt;

&lt;p&gt;Before creating the Pipeline, some pre-conditions on the DynamoDB table need to be fulfilled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Point-in-time-recovery (PITR) is enabled&lt;/li&gt;
&lt;li&gt;DynamoDB Streams are enabled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Besides that, we need an S3 bucket that will be used for the data export and import. I created this bucket upfront.&lt;/p&gt;

&lt;p&gt;And finally, we need an IAM Role that the Pipeline will assume to carry out all the work. Accordingly, the Role Policy must allow all actions that the Pipeline needs to perform on these resources.&lt;/p&gt;

&lt;h4&gt;
  
  
  IAM Role Policy for the Pipeline
&lt;/h4&gt;

&lt;p&gt;For convenience, I am providing the Policy below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"allowRunExportJob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:DescribeTable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:DescribeContinuousBackups"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:ExportTableToPointInTime"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:dynamodb:{region}:{aws-account-no}:table/{table-name}"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"allowCheckExportjob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:DescribeExport"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:dynamodb:{region}:{aws-account-no}:table/{table-name}/export/*"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"allowReadFromStream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:DescribeStream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:GetRecords"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"dynamodb:GetShardIterator"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:dynamodb:{region}:{aws-account-no}:table/{table-name}/stream/*"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"allowReadAndWriteToS3ForExport"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:AbortMultipartUpload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObjectAcl"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::{bucket-name}/*"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"allowDescribeDomain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es:DescribeDomain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:es:*:{aws-account-no}:domain/*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"allowESHttp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es:ESHttp*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{opensearch-domain-arn}"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The role needs to have a Trust relationship with &lt;code&gt;osis-pipelines.amazonaws.com&lt;/code&gt;. Once we make sure all of the above is set, we can proceed with creating the Pipeline.&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating the Ingestion Pipeline
&lt;/h4&gt;

&lt;p&gt;There are several options you need to pick and choose when creating a Pipeline. I'll focus only on a sub-set which proved useful to me for this example. After choosing the name, there is the Pipeline capacity configuration. It is measured in units called Ingestion-OCU and it is billed hourly while the Pipeline is Active, regardless of whether it is actually processing data. The Ingestion Pipeline billing resmbles the Serverless option of OpenSearch Service. I just left the minimum and maximum values to 1 and 4, as it was provided by default.&lt;/p&gt;

&lt;p&gt;The Pipeline configuration is a YAML file with a lot of options, but luckily there are ready made blueprints for common use cases. I chose the &lt;code&gt;DynamoDbChangeDataCapturePipeline&lt;/code&gt; blueprint, which gives a nice template with some sensible defaults for this use case. The configuration ends up looking like the one below. I intentionally left the comments from the blueprint and added some of my own.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;###&lt;/span&gt;
  &lt;span class="c1"&gt;# dynamodb-pipeline:&lt;/span&gt;
  &lt;span class="c1"&gt;# This pipeline is capable of exporting a DynamoDB table to OpenSearch, and processing DynamoDB streams to&lt;/span&gt;
  &lt;span class="c1"&gt;# keep the DynamoDB table and OpenSearch cluster in sync for create, update, and delete Events.&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
&lt;span class="na"&gt;dynamodb-pipeline&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;dynamodb&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;acknowledgments&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;tables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# REQUIRED: Supply the DynamoDB table ARN and whether export or stream processing is needed, or both&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;table_arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{table-arn}"&lt;/span&gt;
          &lt;span class="c1"&gt;# Remove the stream block if only export is needed&lt;/span&gt;
          &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;start_position&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LATEST"&lt;/span&gt;
          &lt;span class="c1"&gt;# Remove the export block if only stream is needed&lt;/span&gt;
          &lt;span class="na"&gt;export&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# REQUIRED for export: Specify the name of an existing S3 bucket for DynamoDB to write export data files to&lt;/span&gt;
            &lt;span class="na"&gt;s3_bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{bucket-name}"&lt;/span&gt;
            &lt;span class="c1"&gt;# Specify the region of the S3 bucket&lt;/span&gt;
            &lt;span class="na"&gt;s3_region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{region}"&lt;/span&gt;
            &lt;span class="c1"&gt;# Optionally set the name of a prefix that DynamoDB export data files are written to in the bucket.&lt;/span&gt;
            &lt;span class="na"&gt;s3_prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ddb-to-opensearch-export/"&lt;/span&gt;
      &lt;span class="na"&gt;aws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="c1"&gt;# REQUIRED: Provide the role to assume that has the necessary permissions to DynamoDB, OpenSearch, and S3.&lt;/span&gt;
         &lt;span class="na"&gt;sts_role_arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{role_arn}"&lt;/span&gt;
         &lt;span class="c1"&gt;# Provide the region to use for aws credentials&lt;/span&gt;
         &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{region}"&lt;/span&gt;
  &lt;span class="na"&gt;sink&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;opensearch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# REQUIRED: Provide an AWS OpenSearch endpoint&lt;/span&gt;
        &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{opensearch_endpoint}"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;table-index"&lt;/span&gt; &lt;span class="c1"&gt;# Define the name of the index for this data&lt;/span&gt;
        &lt;span class="na"&gt;index_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;custom&lt;/span&gt;
        &lt;span class="na"&gt;document_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${getMetadata(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;primary_key&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;)}"&lt;/span&gt;
        &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${getMetadata(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;opensearch_action&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;)}"&lt;/span&gt;
        &lt;span class="na"&gt;document_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${getMetadata(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;document_version&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;)}"&lt;/span&gt;
        &lt;span class="na"&gt;document_version_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;external"&lt;/span&gt;
        &lt;span class="na"&gt;aws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# REQUIRED: Provide a Role ARN with access to the domain. This role should have a trust relationship with osis-pipelines.amazonaws.com&lt;/span&gt;
          &lt;span class="na"&gt;sts_role_arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{role_arn}"&lt;/span&gt; &lt;span class="c1"&gt;# This needs to be the same role as defined in the previous section&lt;/span&gt;
          &lt;span class="c1"&gt;# Provide the region of the domain.&lt;/span&gt;
          &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{region}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apart from the network configuration, ensuring that the Pipeline logs are sent to CloudWatch proved to be very useful for me, especially during the setup, as I wasn't able to get this working immediately. This was mostly due to misconfiguring the Role Policy. While there are many more options for high-availability and durability, those are out of scope for this discussion.&lt;/p&gt;

&lt;p&gt;So, that should be it. Once the Pipeline is created, it should be started, and in a few minutes, if everything is configured properly, you should see the data appear in the designated S3 bucket for export, as well as in the OpenSearch Index, of course. As I already mentioned, CloudWatch logs were a big time-saver when something went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Showcase
&lt;/h2&gt;

&lt;p&gt;As I mentioned at the start, I already had a table full of chat messages with which I tested this setup. For the sake of testing, I am providing this gist which contains CSV representation of an AI generated group chat simulation about a trip to Hawaii 🌴 Feel free to use this for practicing and load it into a DynamoDB table.&lt;/p&gt;

&lt;p&gt;After the pipeline runs the initial import, I am able to search all the data very quickly. Also, each new message ends up in OpenSearch within seconds. To test the search, let's say that I want to find out if the trip is confirmed and hopefully get more info on that. By issuing a very simple &lt;code&gt;GET _search/q=confirmed&lt;/code&gt; request in OpenSearch Dashboards DevTools, the result can be seen in the screenshot below.&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%2Fww6pnmfravwn7zj3ufh1.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%2Fww6pnmfravwn7zj3ufh1.png" alt="opensearch dashboards" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We see that there is one hit and the message contains just what we are looking for. Also, please notice that the &lt;code&gt;_id&lt;/code&gt; field contains the primary key of our DynamoDB table, which consists of a group chat id and a message timestamp. These mappings are performed automatically for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;This article shows that by leveraging the DynamoDB to OpenSearch zero-ETL integration, we can quickly and efficiently search for messages based on keywords without the need for complex infrastructure management. This goes for both the cluster and Serverless variants. The integration also allows us to scale our search solution as our application grows. Overall, Amazon DynamoDB and Amazon OpenSearch are a powerful combination that can help organizations build fast and efficient search solutions for their applications.&lt;/p&gt;

&lt;p&gt;Nevertheless, I believe that the scale of data for both ingestion and search needs to be substantial to warrant the incorporation of OpenSearch into a solution of this nature. Considering that search is not the primary feature of our application, the associated costs do not justify its implementation. Given our architecture, operating the smallest OpenSearch instance along with ingestion for a weekend incurs expenses equivalent to running the entire application for an entire month. OpenSearch Serverless, despite being even more costly, is still unnecessary for us since our current workload is far from reaching the scale that would warrant such a solution. As mentioned earlier, the primary objective of this article is to delve into the new feature of zero-ETL integration. It is likely that we will opt for an alternative solution for search that aligns better with our smaller scale and budget.&lt;/p&gt;

</description>
      <category>dynamodb</category>
      <category>opensearch</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Single Sign-On with Azure AD and Amazon Cognito using OIDC and AWS Amplify</title>
      <dc:creator>Marko Djakovic</dc:creator>
      <pubDate>Wed, 17 May 2023 08:17:11 +0000</pubDate>
      <link>https://dev.to/aws-builders/integrate-azure-ad-and-amazon-cognito-using-oidc-and-aws-amplify-4cf0</link>
      <guid>https://dev.to/aws-builders/integrate-azure-ad-and-amazon-cognito-using-oidc-and-aws-amplify-4cf0</guid>
      <description>&lt;p&gt;Single sign-on (SSO) is often the preferred way of accessing applications as it relieves users from the burden of having to remember yet another, probably insecure password. In my latest project, it was very convenient to have SSO as all our users are using Microsoft accounts. We implemented identity federation between Azure AD and our Cognito User Pool using OpenID Connect. This post will quickly show how to do it according to best practices using the &lt;a href="https://github.com/aws-amplify/amplify-js" rel="noopener noreferrer"&gt;amplify-js&lt;/a&gt; library, while also including CloudFormation templates for the User Pool and accompanying resources.&lt;/p&gt;

&lt;p&gt;My assumptions about the readers of this post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have your own Azure Active Directory running and are able to configure an App Registration&lt;/li&gt;
&lt;li&gt;You have an AWS account and aws-cli configured&lt;/li&gt;
&lt;li&gt;You already have or can run a simple front-end project in any of the popular front-end frameworks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Side note: you can read this also on my personal website - &lt;a href="https://marko.dj/posts/2023-05-17-single-sign-on-azure-cognito-amplify/" rel="noopener noreferrer"&gt;marko.dj - Single Sign-On with Azure AD and Amazon Cognito using OIDC and AWS Amplify&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up an App Registration in Azure AD
&lt;/h2&gt;

&lt;p&gt;In the Azure Portal, we need to create a new App Registration that represents our application and will be used as an OIDC provider with Cognito. After creating it, we will also need to generate a new secret under the "Certificates &amp;amp; secrets" section. Once generated, copy the secret and securely store it in the &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html" rel="noopener noreferrer"&gt;Systems Manager Parameter Store&lt;/a&gt; in your AWS account. Do the same with the client id, which is located in the Overview page of your App Registration under the label "Application (client) ID". We will reference it together with the client id in the CloudFormation template in the next section, so make sure to give these two parameters some meaningful names, for example &lt;code&gt;my-azure-clientid&lt;/code&gt; and &lt;code&gt;my-azure-clientsecret&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Amazon Cognito
&lt;/h2&gt;

&lt;p&gt;All of the required AWS resources will be defined in a single CloudFormation stack, e.g. in a file named &lt;code&gt;cognito.yml&lt;/code&gt;. First, we define a User Pool and a User Pool Domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;MyUserPool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Cognito::UserPool&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;UserPoolName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-user-pool&lt;/span&gt;
  &lt;span class="na"&gt;MyUserPoolDomain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Cognito::UserPoolDomain&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-user-pool&lt;/span&gt; &lt;span class="c1"&gt;# domain name&lt;/span&gt;
      &lt;span class="na"&gt;UserPoolId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MyUserPool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The User Pool Domain will be referenced by Azure AD during the authentication flow. When deployed, the domain will receive a value similar to &lt;code&gt;https://my-user-pool.auth.{region}.amazoncognito.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After that, we add an OIDC User Pool Identity Provider and a corresponding User Pool Client in the &lt;code&gt;cognito.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;MyUserPoolIdentityProvider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Cognito::UserPoolIdentityProvider&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;UserPoolId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MyUserPool&lt;/span&gt;
    &lt;span class="na"&gt;ProviderName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MyCompany"&lt;/span&gt; &lt;span class="c1"&gt;# your custom name&lt;/span&gt;
    &lt;span class="na"&gt;ProviderType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OIDC"&lt;/span&gt;
    &lt;span class="na"&gt;ProviderDetails&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${ssm:my-azure-clientid}&lt;/span&gt;
      &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${ssm:my-azure-clientsecret}&lt;/span&gt;
      &lt;span class="na"&gt;attributes_request_method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GET'&lt;/span&gt;
      &lt;span class="na"&gt;oidc_issuer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://login.microsoftonline.com/{tenant_id}/v2.0'&lt;/span&gt;
      &lt;span class="na"&gt;authorize_scopes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;profile&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;openid'&lt;/span&gt;
    &lt;span class="na"&gt;AttributeMapping&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name'&lt;/span&gt;
      &lt;span class="na"&gt;preferred_username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;preferred_username'&lt;/span&gt;
      &lt;span class="na"&gt;given_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unique_name'&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email'&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sub'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pay attention to the &lt;code&gt;oidc_issuer&lt;/code&gt; field below - the &lt;code&gt;tenant_id&lt;/code&gt; should be swapped with the real value under the label "Directory (tenant) ID" on the App Registration's Overview page. The AttributeMapping section specifies how to map certain attributes from the authenticated user from AD to the Cognito User Pool. That way, we automatically capture the user's name and email address, for example.&lt;/p&gt;

&lt;p&gt;The final component to make this come together is a User Pool Client, specified below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;MyUserPoolClient&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Cognito::UserPoolClient&lt;/span&gt;
  &lt;span class="na"&gt;DependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MyUserPoolIdentityProvider&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;AllowedOAuthFlows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;code&lt;/span&gt;
    &lt;span class="na"&gt;AllowedOAuthFlowsUserPoolClient&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;
    &lt;span class="na"&gt;AllowedOAuthScopes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;email&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;openid&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;profile&lt;/span&gt;
    &lt;span class="na"&gt;CallbackURLs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://localhost:4200'&lt;/span&gt;
    &lt;span class="na"&gt;LogoutURLs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://localhost:4200'&lt;/span&gt;
    &lt;span class="na"&gt;ClientName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-user-pool-client&lt;/span&gt;
    &lt;span class="na"&gt;PreventUserExistenceErrors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ENABLED"&lt;/span&gt;
    &lt;span class="na"&gt;SupportedIdentityProviders&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MyCompany&lt;/span&gt; &lt;span class="c1"&gt;# Refer to the OIDC provider name&lt;/span&gt;
    &lt;span class="na"&gt;UserPoolId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MyUserPool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach uses the authorization_code flow, which is defined under &lt;code&gt;AllowedOAuthFlows&lt;/code&gt;. You probably notice that &lt;code&gt;CallbackURLs&lt;/code&gt; and &lt;code&gt;LogoutURLs&lt;/code&gt; point to localhost, and by the port value, you can guess that I have an Angular single-page application to test this locally. These two values contain the addresses where the user will be redirected after a successful authentication and logout, respectively. If your front-end app is deployed somewhere, these values should reflect that remote address.&lt;/p&gt;

&lt;p&gt;Deploy this template using aws-cli using the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation deploy &lt;span class="nt"&gt;--stack-name&lt;/span&gt; my-cognito &lt;span class="nt"&gt;--template-file&lt;/span&gt; cognito.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After successful deployment, there is still one more thing to configure in the Azure Portal before the whole flow works. Remember the Cognito User Pool Domain? That is the final piece that will make the AD "see" our User Pool.&lt;/p&gt;

&lt;p&gt;That is configured in the App Registration under Authentication. When clicking the "Add platform" button, a side sheet will open with several options of web and mobile applications to choose from. &lt;strong&gt;Even though we are creating a single-page application, the correct choice here is "Web". That is because the AD is actually interacting with Cognito, not with our single-page app directly.&lt;/strong&gt; After picking the Web platform, in the Redirect URIs input field we paste our User Pool Domain, appended with &lt;strong&gt;"/oauth2/idpresponse"&lt;/strong&gt; so it looks like &lt;code&gt;https://my-user-pool.auth.{region}.amazoncognito.com/oauth2/idpresponse&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using @aws-amplify/auth library
&lt;/h2&gt;

&lt;p&gt;Why do I think you should use this library? Firstly, it performs all the communication with &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-reference.html" rel="noopener noreferrer"&gt;the Cognito API&lt;/a&gt;. More importantly, it does so using security best current practices, like using &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-proof-key-for-code-exchange-pkce" rel="noopener noreferrer"&gt;authorization code with PKCE&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As I mentioned, I used Angular to test this, but any other framework should have it in a similar way. After adding &lt;code&gt;@aws-amplify/auth&lt;/code&gt; and &lt;code&gt;@aws-amplify/core&lt;/code&gt; dependencies to the project, Amplify Auth should be configured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Amplify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userPoolId&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="na"&gt;userPoolWebClientId&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="na"&gt;oauth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;domain&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="na"&gt;scope&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;openid&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;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="s1"&gt;profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;redirectSignIn&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:4200&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;redirectSignOut&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:4200&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;responseType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The properties &lt;code&gt;userPoolId&lt;/code&gt;, &lt;code&gt;userPoolWebClientId&lt;/code&gt;, and &lt;code&gt;domain&lt;/code&gt; can be found in the AWS Console under the Cognito deployment, and &lt;code&gt;region&lt;/code&gt; is the AWS region where the stack is deployed. On your app's login page, a button to initiate the login flow should call the following method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;federatedSignIn&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;customProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyProvider&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;The user will be redirected to log in with the provider (Microsoft), after which they will be redirected back to the app where Amplify Auth will automatically exchange the given authorization code for tokens from Cognito. All of this is done securely and according to the best practices.&lt;/p&gt;

&lt;p&gt;To get the currently logged-in user, a call to the &lt;code&gt;Auth.currentAuthenticatedUser()&lt;/code&gt; method should do the job.&lt;/p&gt;

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

&lt;p&gt;By following the steps outlined in this post, you can set up the necessary AWS resources and Azure configurations to try out SSO using the amplify-js library. It's worth noting an interesting observation: &lt;a href="https://aws.amazon.com/cognito/pricing/" rel="noopener noreferrer"&gt;Cognito pricing&lt;/a&gt; for users logging in with SAML or OIDC is almost 3 times higher compared to regular users. Also, the free tier is capped at only 50 OIDC monthly active users, compared to 50k regular monthly active users. For example, if you are out of the free tier, and have 1000 monthly active users using OIDC, that would cost $15. &lt;/p&gt;

&lt;p&gt;I hope you found this guide helpful and informative. If you have any questions or would like to share your experiences with implementing SSO using Cognito, I invite you to leave your comments below. I look forward to engaging with you and addressing any inquiries you may have.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>sso</category>
      <category>amplify</category>
      <category>aws</category>
    </item>
    <item>
      <title>How AWS and Serverless Revived Our MVP Development Journey</title>
      <dc:creator>Marko Djakovic</dc:creator>
      <pubDate>Sun, 23 Apr 2023 23:10:01 +0000</pubDate>
      <link>https://dev.to/aws-builders/how-aws-and-serverless-revived-our-mvp-development-journey-5hjd</link>
      <guid>https://dev.to/aws-builders/how-aws-and-serverless-revived-our-mvp-development-journey-5hjd</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;A team of talented individuals embarks on a journey to build something cool for the rest of their colleagues to help them in day to day life at the office. This bright idea of a super powerful, social-network-like app for company employees emerged. User flows are quickly sketched out, dozens of features planned, technical diagrams are drawn, we had it all figured out and were super motivated to start. On top of that, we'll build it and host it ourselves on some VMs on premise!&lt;/p&gt;

&lt;p&gt;You may have guessed - it didn't see the light of day.&lt;/p&gt;

&lt;p&gt;In this blog post, I'll share why this approach failed and how, in a second attempt and by adopting AWS and Serverless, we successfully developed and deployed a functioning MVP on a limited time and budget. You can also read it on my personal website - &lt;a href="https://marko.dj/posts/2023-04-23-how-aws-serverless-revived-mvp-journey/" rel="noopener noreferrer"&gt;marko.dj - How AWS and Serverless Revived Our MVP Development Journey&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Attempt: A Lesson in Complexity
&lt;/h2&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%2F54s3hussbmz4f0nqnw7a.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%2F54s3hussbmz4f0nqnw7a.png" alt="Kubernetes meme" width="417" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have our architecture laid out and it's time to make those diagrams a reality. We're going to deploy Kubernetes to our existing servers with Apache Kafka as the backbone for the system. The app is going to be packed with features so it needs to be powerful, resilient and future-proof and what not.&lt;br&gt;
Setting up the Kubernetes cluster required significant effort, even with the help of our seasoned ops colleagues, but eventually it was up and running on 5 nodes, managed in house. Next, configure Kafka and integrate it with several microservices that are being developed in the meantime - also not a walk in the park. Neither was setting up self-managed GitLab Runners for deploying the microservices.&lt;/p&gt;

&lt;p&gt;I could go on with a few more things, but I hope you see where this is going. Not a single feature was really finished! And when I say finished, I don't mean works perfectly or has no bugs, I mean just deployed and working OK. And we had a team with plenty of expertise, so the reason definitely wasn't lack of talent.&lt;/p&gt;

&lt;p&gt;Managing all the infrastructure components took a big bite of our time and diverted our attention from building core features of our product.&lt;/p&gt;

&lt;p&gt;Soon enough, people had to focus on different priorities within the company, so the team disassembled and the project was abandoned.&lt;/p&gt;

&lt;h2&gt;
  
  
  A New Approach: Embracing Serverless and AWS
&lt;/h2&gt;

&lt;p&gt;Some (a lot of) time passed, our company adopted the cloud, everybody gained more experience with it along the way, especially on AWS. Coincidentally, a part of the original team was working together again and, determined to learn from our past mistakes, our idea was about to get a fresh start. The goal now is to get a usable app in the hands of first users as soon as possible. After a few UI and cloud architecture sketches, we were ready to start. We took out one feature from the original, full-blown app idea, and worked out an MVP scope for it. It was basically a web application communicating with a REST API and a WebSocket to support real time group chat. Laser focused on our goal, relying heavily on AWS managed services, the first deployment happened within a week or so. To give you an idea of our starting landscape, take a look at the diagram below.&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%2F04ppvxxxm8kykq8iy84p.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%2F04ppvxxxm8kykq8iy84p.png" alt="AWS Diagram" width="800" height="807"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One piece is missing and that's IoT Core, but I just wanted to use &lt;a href="https://libraries.excalidraw.com/?target=_excalidraw&amp;amp;referrer=https%3A%2F%2Fexcalidraw.com%2F&amp;amp;useHash=true&amp;amp;token=By1EVdWkpdtAPcwU1N4SW&amp;amp;theme=light&amp;amp;version=2&amp;amp;sort=default#stojanovic-aws-serverless-icons-v2" rel="noopener noreferrer"&gt;this new set of AWS icons for Excalidraw&lt;/a&gt; so badly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trimming Down Features: The Importance of Scope Control
&lt;/h2&gt;

&lt;p&gt;While cloud and Serverless enabled us to be productive already in the first week, they will not protect you from scope-creep! Everybody from the team had brilliant ideas on what to implement next. Arguing how "adding just this small thing would be great for this and that", or slipping into a trap of "this will take just 2 additional hours and bring more value". Fiercely cutting down on scope expansion was instrumental in keeping the team focused on the main goal - and that is serving the first group of users as soon as possible.&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%2Ftg81t7di0jzkxu5wn867.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftg81t7di0jzkxu5wn867.jpg" alt="Scope creep meme" width="625" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was especially important as we knew our time to work on this project is quite limited - new assignments in the company were looming around the corner.&lt;/p&gt;

&lt;h2&gt;
  
  
  User Feedback Or Where Are The Emojis?
&lt;/h2&gt;

&lt;p&gt;We are building something for our colleagues, for internal use, basically solving our own problems, and that gives us good insight in which features we need to build first, right? Surprisingly, the feedback from every single person from our first group of pilot users was that the chat needed to be richer, more precisely to support emojis and emoji reactions on messages. On the other hand, people are really used to rich chat features, since they have them in every actual chat app. Even if it wasn't considered crucial from our point of view, we realized that missing this could lower the chances of anyone using our app. It was the first thing on our backlog the following week.&lt;/p&gt;

&lt;p&gt;So, we missed an important feature, but the fast feedback enabled by our cloud powered development setup makes it easy for us to quickly iterate and add the missing value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps: Preparing for Production
&lt;/h2&gt;

&lt;p&gt;We are still working on a few quick wins that our first group of users suggested, while aiming to tighten a few more knots before we open the app to all users. Currently, it's &lt;a href="https://gojko.net/2012/05/08/redefining-software-quality/" rel="noopener noreferrer"&gt;deployable and we believe it is functionally OK, so next step is validating that it's secure, reliable and performant.&lt;/a&gt;&lt;br&gt;
Security will be checked by penetration testing and to make sure the app is reliable and that we spot any performance bottlenecks quickly, we've setup CloudWatch monitoring dashboards and alarms. Having an overview of metrics like number of API requests, latency, error count, and alarms on any server error enables us to react pro-actively. This would've been much harder if we were running this on-prem.&lt;/p&gt;

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

&lt;p&gt;This story is an example of how AWS and its Serverless services can save time and resources when building under constraints. Apart from that, it touches the subject of scope-creep and the importance of keeping it under control when building an MVP. Also, discusses the steps to take after, like ramping up security and considering ways to measure the general quality and success of the product as it evolves.&lt;/p&gt;

&lt;p&gt;Not to get ahead of ourselves - we still have some work to do before we can think about measuring if the product is successful. However, for things like basic monitoring, alarming and automation - AWS makes this ridiculously easy to do that it's a no-brainer.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>mvp</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Deploying a Spring Boot API to AWS with Serverless and Lambda SnapStart</title>
      <dc:creator>Marko Djakovic</dc:creator>
      <pubDate>Tue, 31 Jan 2023 16:27:15 +0000</pubDate>
      <link>https://dev.to/aws-builders/deploying-a-spring-boot-api-to-aws-with-serverless-and-lambda-snapstart-439p</link>
      <guid>https://dev.to/aws-builders/deploying-a-spring-boot-api-to-aws-with-serverless-and-lambda-snapstart-439p</guid>
      <description>&lt;p&gt;In my last two blog posts I wrote about &lt;a href="https://marko.dj/posts/2022-04-20-deploy-nestjs-api-aws-lambda-serverless/" rel="noopener noreferrer"&gt;NestJS Mono-Lambda&lt;/a&gt; and &lt;a href="https://marko.dj/posts/2022-07-08-aws-lambda-cold-starts-nestjs/" rel="noopener noreferrer"&gt;how we dealt with cold starts&lt;/a&gt;. Cold starts are not that big of an issue when running NodeJS Lambdas, but with Java the numbers are unacceptable if you're building synchronous, user-facing APIs. &lt;a href="https://www.youtube.com/watch?v=ZbnAithBNYY" rel="noopener noreferrer"&gt;At re:Invent 2022 AWS announced SnapStart&lt;/a&gt;, a feature for Lambda using Java runtime. I always liked the simplicity of deployment to the cloud that Serverless Framework gives, combined with the developer experience of frameworks like NestJS and SpringBoot. After years of developing TypeScript applications on AWS Lambda, I wanted to go back to my first love, Spring Framework, to build an API and test SnapStart in the process.&lt;/p&gt;

&lt;p&gt;This post (also available on &lt;a href="https://marko.dj/posts/2023-01-31-deploy-spring-boot-api-aws-serverless-lambda-snapstart/" rel="noopener noreferrer"&gt;my personal website marko.dj&lt;/a&gt;) will cover how to create an API that returns the closest Starbucks locations near the given point and radius. The API will be backed by MongoDB to utilize geospatial queries, with a Spring Boot app wrapped into a Mono-Lambda on top, deployed seamlessly to AWS with Serverless Framework. When it's all up in the cloud and working, we will take a closer look at the performance and try to optimize it further by utilizing AWS Lambda SnapStart.&lt;/p&gt;

&lt;p&gt;Pre-requisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your own AWS account (this exercise fits in the free-tier)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.serverless.com/framework/docs/providers/aws/guide/credentials/" rel="noopener noreferrer"&gt;Serverless Framework installed and configured&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup MongoDB Atlas
&lt;/h2&gt;

&lt;p&gt;I signed up for a free MongoDB Atlas account and launched a free cluster in AWS Frankfurt region. To add the test data to the database, I found a collection of US Starbucks locations and &lt;a href="https://gist.github.com/imflamboyant/8acad0d54bd3397b31a1812a88c49067" rel="noopener noreferrer"&gt;forked it&lt;/a&gt; to support &lt;a href="https://www.mongodb.com/docs/manual/reference/geojson/" rel="noopener noreferrer"&gt;GeoJSON objects&lt;/a&gt; for position. This was needed because I want to index the items in order to be able to perform efficient geospatial queries that Mongo offers. Next, I created a &lt;code&gt;2dsphere&lt;/code&gt; index on the &lt;code&gt;position&lt;/code&gt; field. To test if index works, we can execute the following query on MongoDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    position: {
        $nearSphere: {
            type: "Point",
            coordinates: [ -73.9655834, 40.7825547 ] // Central Park
        },
        $maxDistance: 1000 // 1km
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should return some results near Central Park. To read more about geospatial queries in MongoDB, feel free to refer to the &lt;a href="https://www.mongodb.com/docs/manual/geospatial-queries/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;. Now our data is ready, let's proceed with creating our Spring Boot application that will query Starbucks locations.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: For the purpose of this exercise make sure that in the MongoDB Atlas Security section, Network Access IP Access List contains the &lt;code&gt;0.0.0.0/0&lt;/code&gt; rule. That will make the database publicly accessible. None of this is a good idea to do in production, including usage of this free, shared-tier Mongo cluster. More secure way is the use VPC peering, however that is beyond the scope of this article, and it is also not included in the shared-tier MongoDB Atlas cluster. 💰&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Spring Boot API
&lt;/h2&gt;

&lt;p&gt;To quickly get up and running with development, let's use &lt;a href="https://start.spring.io/" rel="noopener noreferrer"&gt;Spring Initializr&lt;/a&gt;. After selecting Java 11, Maven, Spring Boot 2.7.8 project properties, and picking Spring Web and Spring Data MongoDB dependencies, we get a project that is ready for further development. Now, let's create the usual three-layered Repository, Service, Controller setup. We'll start by creating a &lt;code&gt;StarbucksDocument&lt;/code&gt; that represents one MongoDB document:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.data.annotation.Id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.data.mongodb.core.geo.GeoJsonPoint&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.data.mongodb.core.mapping.Document&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Document&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"starbucks"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StarbucksDocument&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;GeoJsonPoint&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// getters and setters...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leveraging Spring Data MongoDB module, we can create a repository by just extending &lt;code&gt;MongoRepository&lt;/code&gt; interface. We get the standard CRUD operations out of the box, so we just need to add a method for querying by &lt;code&gt;position&lt;/code&gt;. This way we can fetch stores that are within a given &lt;code&gt;distance&lt;/code&gt; from the passed in &lt;code&gt;position&lt;/code&gt;, page by page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.data.domain.Page&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.data.domain.Pageable&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.data.geo.Distance&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.data.mongodb.core.geo.GeoJsonPoint&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.data.mongodb.repository.MongoRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;StarbucksRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;MongoRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StarbucksDocument&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Page&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StarbucksDocument&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findByPositionNear&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GeoJsonPoint&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Distance&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Pageable&lt;/span&gt; &lt;span class="n"&gt;pageable&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository layer is set up, and we will create a &lt;code&gt;StarbucksService&lt;/code&gt; and a &lt;code&gt;StarbucksController&lt;/code&gt; so that our API looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET /starbucks/{id}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /starbucks?lng=-73.9655834&amp;amp;lat=40.7825547&amp;amp;distance=2&amp;amp;page=x&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build and test this, it wouldn't work of course, because of the missing database connection configuration in &lt;code&gt;application.properties&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.data.mongodb.uri&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;mongodb+srv://username:password@clusterxxx.xyz.mongodb.net/dbname?retryWrites=true&amp;amp;w=majority&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to swap the values with your own, obtained from MongoDB Atlas.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 Run it, test it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./mvnw spring-boot:run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'http://localhost:8080/starbucks?lng=-73.9655834&amp;amp;lat=40.7825547&amp;amp;distance=1'&lt;/span&gt; | json_pp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"86th &amp;amp; Columbus_540 Columbus Avenue_New York, New York 10024_(212) 496-4139"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"63d52640712420c4e81c9a20"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;40.78646447&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-73.97215027&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Starbucks - NY - New York [W]  06186"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"81st &amp;amp; Columbus_444 Columbus Avenue_New York, New York 10024"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"63d52640712420c4e81c9a26"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;40.78335323&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-73.97441845&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Starbucks - NY - New York [W]  06192"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"87th &amp;amp; Lexington_120 EAST 87TH ST_New York, New York 10128"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"63d52640712420c4e81c9a29"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;40.78052553&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-73.95603158&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Starbucks - NY - New York [W]  06195"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lexington &amp;amp; 85th_1261 Lexington Avenue_New York, New York 10026"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"63d52640712420c4e81c9a41"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;40.778801&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-73.956099&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Starbucks - NY - New York [W]  06219"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apparently we have 4 coffee shops within 1 kilometre of Central Park. Nice. ☕️&lt;/p&gt;

&lt;h2&gt;
  
  
  How to deploy it to AWS?
&lt;/h2&gt;

&lt;p&gt;A few more steps are needed for deploying our API to AWS with the Serverless Framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  Maven dependency
&lt;/h3&gt;

&lt;p&gt;Add the &lt;code&gt;aws-serverless-java-container-springboot2&lt;/code&gt; dependency to &lt;code&gt;pom.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.amazonaws.serverless&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;aws-serverless-java-container-springboot2&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.9.1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Maven profiles
&lt;/h3&gt;

&lt;p&gt;For convenience, create two Maven profiles - &lt;code&gt;local&lt;/code&gt; and &lt;code&gt;shaded-jar&lt;/code&gt;. This makes it easier to separate running the app locally and packaging the jar for AWS Lambda. The &lt;code&gt;shaded-jar&lt;/code&gt; profile contains the &lt;code&gt;spring-boot-starter-web&lt;/code&gt; dependency but without &lt;code&gt;spring-boot-starter-tomcat&lt;/code&gt;. To &lt;a href="https://github.com/awslabs/aws-serverless-java-container/wiki/Quick-start---Spring-Boot2#3-packaging-the-application" rel="noopener noreferrer"&gt;package the app for AWS Lambda&lt;/a&gt;, the &lt;a href="https://maven.apache.org/plugins/maven-shade-plugin" rel="noopener noreferrer"&gt;maven-shade-plugin&lt;/a&gt; is used to package all the dependencies into one big jar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;profiles&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;profile&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;local&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;activation&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;activeByDefault&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/activeByDefault&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/activation&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-web&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;build&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;plugins&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/build&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/profile&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;profile&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;shaded-jar&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-web&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;exclusions&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;exclusion&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-tomcat&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/exclusion&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/exclusions&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;build&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;plugins&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.apache.maven.plugins&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-shade-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.2.4&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;createDependencyReducedPom&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/createDependencyReducedPom&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;package&lt;span class="nt"&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;shade&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;artifactSet&amp;gt;&lt;/span&gt;
                                    &lt;span class="nt"&gt;&amp;lt;excludes&amp;gt;&lt;/span&gt;
                                        &lt;span class="nt"&gt;&amp;lt;exclude&amp;gt;&lt;/span&gt;org.apache.tomcat.embed:*&lt;span class="nt"&gt;&amp;lt;/exclude&amp;gt;&lt;/span&gt;
                                    &lt;span class="nt"&gt;&amp;lt;/excludes&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;/artifactSet&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/build&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/profile&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/profiles&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Implement &lt;code&gt;RequestStreamHandler&lt;/code&gt; for running the app in Lambda. We leverage async initialization described in the &lt;a href="https://github.com/awslabs/aws-serverless-java-container/wiki/Quick-start---Spring-Boot2#asynchronous-initialization" rel="noopener noreferrer"&gt;aws-serverless-java-container docs&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StreamLambdaHandler&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;RequestStreamHandler&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;SpringBootLambdaContainerHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AwsProxyRequest&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AwsProxyResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;handler&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;SpringBootProxyHandlerBuilder&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AwsProxyRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultProxy&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asyncInit&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;springBootApplication&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StarbucksApiApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildAndInitialize&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ContainerInitializationException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// if we fail here, we re-throw the exception to force another cold start&lt;/span&gt;
            &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printStackTrace&lt;/span&gt;&lt;span class="o"&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="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not initialize Spring Boot application"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;handleRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InputStream&lt;/span&gt; &lt;span class="n"&gt;inputStream&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OutputStream&lt;/span&gt; &lt;span class="n"&gt;outputStream&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;proxyStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputStream&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outputStream&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;And the final touch, to configure our Serverless Framework deployment by creating &lt;code&gt;serverless.yml&lt;/code&gt; file in the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;starbucks-api&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;3'&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;java11&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-central-1&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;DBPW&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${ssm:dbpw}&lt;/span&gt;

&lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;artifact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;target/starbucks-api-0.0.1-SNAPSHOT.jar&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;api&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;com.starbucks.api.lambda.StreamLambdaHandler::handleRequest&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
    &lt;span class="na"&gt;memorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2048&lt;/span&gt;
    &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/{proxy+}&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;any&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;DBPW&lt;/code&gt; environment variable? I used AWS Systems Manager Parameter Store to securely store the database password. You should set the &lt;code&gt;DBPW&lt;/code&gt; environment variable locally when building the project, if you have any tests that load the Spring's Context. Feel free to experiment with the &lt;code&gt;memorySize&lt;/code&gt; and &lt;code&gt;timeout&lt;/code&gt; values.&lt;/p&gt;

&lt;p&gt;Finally, build it and deploy it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./mvnw clean package &lt;span class="nt"&gt;-P&lt;/span&gt; shaded-jar
serverless deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Maven creates the shaded jar and then the Serverless Framework deploys it to AWS Lambda.&lt;/p&gt;

&lt;p&gt;The first invocation takes a whopping 11 seconds, even though I've put 2 gigs of memory for the Lambda. Cold start seems pretty bad. I haven't been able to get a response in less than 10 seconds. Warm invocations run in under 100ms on average.&lt;/p&gt;

&lt;h2&gt;
  
  
  SnapStart to the rescue
&lt;/h2&gt;

&lt;p&gt;Let's try to optimize this immediately by enabling SnapStart. This is done in &lt;code&gt;serverless.yml&lt;/code&gt; by supplying the &lt;code&gt;snapStart: true&lt;/code&gt; option to the &lt;code&gt;api&lt;/code&gt; function and run &lt;code&gt;serverless deploy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What we can immediately notice is that the deployment takes about 2 minutes longer. That is because Lambda creates a snapshot of the function and saves it for future invocations. If we test it again, the improvements are huge - cold start time is between 1 and 1.5 seconds. Note that this is observed only on the function level, end-to-end latency is still above 2 seconds for the cold-start invocation. For some reason, API Gateway adds some more latency than usual.&lt;/p&gt;

&lt;h3&gt;
  
  
  A closer look at duration and pricing
&lt;/h3&gt;

&lt;p&gt;To dive a bit deeper let's take a look at CloudWatch logs for one particular invocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REPORT RequestId: c88a0706-70fa-455b-be3a-1138f981b7e7  Duration: 1124.43 ms    Billed Duration: 1423 ms    Memory Size: 2048 MB    Max Memory Used: 206 MB Restore Duration: 392.10 ms Billed Restore Duration: 298 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few important numbers here, and the first one is obviously &lt;em&gt;Duration&lt;/em&gt;. That is how long it took to execute the function. &lt;em&gt;Billed Duration&lt;/em&gt; is also familiar, but as you can see it is slightly higher than the actual duration. How come? To answer that, we have to look at the two newly added fields - &lt;em&gt;Restore Duration&lt;/em&gt; and &lt;em&gt;Billed Restore Duration&lt;/em&gt;. The first one is how much it took for Lambda to restore the snapshot and the second one is how much of that time we are charged for. Why are we charged less? &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Restore Duration includes the time it takes for Lambda to restore a snapshot, load the runtime (JVM) and run any afterRestore hooks. You are not billed for the time it takes to restore the snapshot. However, the time it takes for the runtime (JVM) to load, execute any afterRestore hook, and signal readiness to serve invocations is counted towards the billed Lambda function duration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Source&lt;/em&gt;: &lt;a href="https://aws.amazon.com/blogs/compute/starting-up-faster-with-aws-lambda-snapstart/" rel="noopener noreferrer"&gt;Starting up faster with AWS Lambda SnapStart&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Further optimization - Runtime Hooks and Priming
&lt;/h3&gt;

&lt;p&gt;Turning SnapStart on improved the cold start significantly, we can safely say that for a lot of use cases with some real production load this would be good enough. But ever since it was introduced, there's been talk about &lt;em&gt;Priming&lt;/em&gt;. Runtime hooks allow you to execute some code just before the snapshot is taken, as well as right after the snapshot is restored by Lambda. I encourage you to read a more detailed explanation about it on &lt;a href="https://aws.amazon.com/blogs/compute/reducing-java-cold-starts-on-aws-lambda-functions-with-snapstart/" rel="noopener noreferrer"&gt;AWS blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this case, priming comes down to trying to make dummy calls to your code, so it gets compiled and saved with the snapshot, with the goal to speed up the next cold start even more. I tried a few things, and I've got mixed results.&lt;/p&gt;

&lt;h4&gt;
  
  
  Database connections
&lt;/h4&gt;

&lt;p&gt;The first thing I did was just made a request from the &lt;code&gt;beforeCheckpoint&lt;/code&gt; hook to one of the endpoints that fetches some data from the database. After deploying to AWS and testing, my cold-start invocation just timed out. I quickly realized that the socket timeout configuration on MongoDB is set to infinity by default, and because the connection to MongoDB was established in the snapshot process, my function was just hanging until the timeout. I changed the &lt;code&gt;socketTimeoutMS&lt;/code&gt; setting to something like 500ms and that seemed to work, as the application quickly "realized" that it needs to create a fresh connection to the database. However, it seems that it did add these 500ms to the cold start time, beating the purpose of priming in the first place.&lt;/p&gt;

&lt;p&gt;Regardless, default config values probably shouldn't be used in most production cases anyway.&lt;/p&gt;

&lt;h4&gt;
  
  
  Priming with dummy calls instead
&lt;/h4&gt;

&lt;p&gt;Not wanting to mess with database connections, I decided to create a dummy request and return a response even before the code tries to fetch something from Mongo. I immediately didn't like this, as it requires some caveats in parts of the application code. However, after first tests it did seem to reduce the cold start by a few hundred milliseconds.&lt;/p&gt;

&lt;p&gt;Let's take a quick look at the results of testing the same API with and without priming. The load was 1000 requests with the concurrency of 10.&lt;/p&gt;

&lt;p&gt;Percentage of the requests served within a certain time (ms) - &lt;em&gt;no priming&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;percentage&lt;/th&gt;
&lt;th&gt;time (ms)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;168&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;98%&lt;/td&gt;
&lt;td&gt;378&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99%&lt;/td&gt;
&lt;td&gt;1317&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;2341&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Percentage of the requests served within a certain time (ms) - &lt;em&gt;with priming&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;percentage&lt;/th&gt;
&lt;th&gt;time (ms)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;155&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;98%&lt;/td&gt;
&lt;td&gt;497&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99%&lt;/td&gt;
&lt;td&gt;1050&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;1799&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;This blog post showed how easy it can be to build a MongoDB backed Spring Boot Mono-Lambda API, with the benefits of effortless deployment to the cloud with Serverless Framework. While the example is far from being production grade, I think it's still an interesting experiment. There's a lot of room to further explore this setup, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;try MongoDB Atlas Serverless&lt;/li&gt;
&lt;li&gt;ramp up security by using VPC peering and Security Groups&lt;/li&gt;
&lt;li&gt;tweak MongoDB connection configuration that would suit potential production use&lt;/li&gt;
&lt;li&gt;test the behavior with slightly larger API&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Useful links
&lt;/h2&gt;

&lt;p&gt;On top of all the links throughout the post, I highly recommend checking out the following as well. These offer additional perspectives and dive deeper into the topic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/vkazulkin/measuring-java-11-lambda-cold-starts-with-snapstart-part-1-first-impressions-30a4"&gt;Measuring Java 11 Lambda Cold Starts With SnapStart by Vadym Kazulkin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.symphonia.io/posts/2023-01-11_snapstart-what-why" rel="noopener noreferrer"&gt;AWS Lambda SnapStart - What, and Why by Mike Roberts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=A1rYiHTy9Lg&amp;amp;list=PLCOG9xkUD90IDm9tcY-5nMK6X6g8SD-Sz" rel="noopener noreferrer"&gt;YouTube playlist about SpringBoot &amp;amp; AWS Lambda by James Eastham&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading and feel free to reach out to discuss any of the related topics.&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>AWS Lambda Cold Starts: The Case of a NestJS Mono-Lambda API</title>
      <dc:creator>Marko Djakovic</dc:creator>
      <pubDate>Fri, 08 Jul 2022 16:17:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-lambda-cold-starts-the-case-of-a-nestjs-mono-lambda-api-4j42</link>
      <guid>https://dev.to/aws-builders/aws-lambda-cold-starts-the-case-of-a-nestjs-mono-lambda-api-4j42</guid>
      <description>&lt;p&gt;Earlier this year I wrote about a NestJS API packed in an AWS Lambda. If you haven't read it, feel free to &lt;a href="https://marko.dj/posts/2022-04-20-deploy-nestjs-api-aws-lambda-serverless/" rel="noopener noreferrer"&gt;check it out&lt;/a&gt; if you'd like to learn how to deploy an API quickly using Serverless framework. This article is a follow-up on that one and it will explore the famous "cold start" issue with Lambda functions, particularly in this case of a so called mono-lambda or fat lambda. You can also read it in full on my personal website - &lt;a href="https://marko.dj/posts/2022-07-08-aws-lambda-cold-starts-nestjs/" rel="noopener noreferrer"&gt;marko.dj - AWS Lambda Cold Starts: The Case of a NestJS Mono-Lambda API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To test and demonstrate the cold start statistics, we will be hitting the Songs API with a variable load of requests for a couple of minutes. Then, we'll briefly analyze the results by looking at the Latency metric for the API Gateway and other useful Lambda metrics like (Init) Duration and Concurrent Executions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a cold start?
&lt;/h2&gt;

&lt;p&gt;There are a lot of articles already explaining this, so I'll be super short and use an image below, referenced from &lt;a href="https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-1" rel="noopener noreferrer"&gt;AWS&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%2Fzaww7lf3mglwfzlnhmzf.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%2Fzaww7lf3mglwfzlnhmzf.png" alt="coldstarts)" width="800" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bottom line is your Lambda will be a lot slower the first time it runs compared to the invocations that follow on an already "warm" instance. This means that even if the majority of requests are processed really fast, those that are impacted by the cold start will be noticeably slower.&lt;/p&gt;

&lt;h2&gt;
  
  
  How did we come to this?
&lt;/h2&gt;

&lt;p&gt;We've already been running one project in production with more than a 100 Lambdas written in TypeScript. When the second project came, we wanted to try something different - develop an API using NestJS, a framework we haven't worked with before but heard nice things about.&lt;/p&gt;

&lt;p&gt;Since we relied on CloudWatch to monitor our APIs, the usual approach we saw on tutorials with only one http event that has method &lt;code&gt;any&lt;/code&gt; and &lt;code&gt;/{proxy+}&lt;/code&gt; path wouldn't work for us. We wanted detailed API monitoring per endpoint, so first we did something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&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;getSongs&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;dist/lambda.handler&lt;/span&gt;
    &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;songs&lt;/span&gt;
  &lt;span class="na"&gt;getOneSong&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;dist/lambda.handler&lt;/span&gt;
    &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;songs/{id}&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, a function per endpoint, but each function actually pointing to a whole NestJS app. And very soon we had something to see on our monitoring dashboard - this was getting pretty slow because each request was spinning up a new Lambda most of the times. And the application wasn't even that big yet.&lt;/p&gt;

&lt;p&gt;We started looking for solutions to the cold start problem. One pretty common was to use libraries or workarounds that are "warming up" the Lambdas using a scheduled pinging mechanism. On why this approach cannot guarantee a warm Lambda execution, you can read more on AWS: &lt;a href="https://docs.aws.amazon.com/lambda/latest/operatorguide/execution-environments.html" rel="noopener noreferrer"&gt;Lambda execution environments&lt;/a&gt;. However, if you're interested to experiment with this, there's a plugin for Serverless Framework called &lt;a href="https://www.serverless.com/plugins/serverless-plugin-warmup" rel="noopener noreferrer"&gt;Serverless Plugin Warmup&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The second approach is sort of an official recommendation from AWS - &lt;a href="https://aws.amazon.com/blogs/aws/new-provisioned-concurrency-for-lambda-functions/" rel="noopener noreferrer"&gt;provisioned concurrency&lt;/a&gt;. Worrying about scaling beats the purpose of going serverless, right? More importantly, the API we were building was relatively new and we didn't have usage patterns which would help define the provisioned concurrency. And of course the price was a big factor, as it would cost the same as some average EC2 instance 💸&lt;/p&gt;

&lt;p&gt;So, we ended up deploying this mono-lambda, but with a slight change to our Serverless configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&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;api&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;dist/lambda.handler&lt;/span&gt;
    &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;songs&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;songs/{id}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;post&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;songs&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This results in all the API Gateway resources getting created, enabling us to have detailed monitoring per endpoint while  minimizing the number of cold starts, since it's just one Lambda with multiple &lt;code&gt;http&lt;/code&gt; events.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: In order to have metrics per API Gateway resource (endpoint), Detailed CloudWatch Metrics option needs to be enabled. It won't be enabled for the purposes of this article, as it incurs some additional costs. More information can be found on &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-metrics-and-dimensions.html" rel="noopener noreferrer"&gt;API Gateway metrics and dimensions&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%2Frmngs75uohrt6hsg4wxv.jpeg" 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%2Frmngs75uohrt6hsg4wxv.jpeg" alt="loadtestmeme" width="450" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I just want to hit this API with some requests so we can see the results in the monitoring and observe available metrics. With the help of &lt;a href="https://httpd.apache.org/docs/2.4/programs/ab.html" rel="noopener noreferrer"&gt;Apache Bench&lt;/a&gt; tool I can do just that with a few lines of bash code. The load is an average of 10 requests per second for about 2 minutes. Let's discuss the results real quick.&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%2Fu29iocs9n57152p6rhid.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%2Fu29iocs9n57152p6rhid.png" alt="apimaxlatency" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The diagram above shows &lt;em&gt;maximum&lt;/em&gt; latency which is very high at the beginning, due to the cold start. It falls drastically and quickly for sub-sequent requests as the Lambdas are warm. So how many cold starts did we have during this small experiment? The metrics say there are 4 concurrent Lambda executions, which means we got 4 cold starts. &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%2F0ybx8kjw9afmpy8hlhsk.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%2F0ybx8kjw9afmpy8hlhsk.png" alt="conclambda" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For about 1000 requests, that's less than half percent. For these slow requests, lasting around 1.5 seconds, it was Lambda's &lt;em&gt;Init Duration&lt;/em&gt; that was taking up about 1 to 1.1 seconds. The rest was Lambda handler initialization + function execution. We can see the most expensive executions, lasting under 400ms each, on the image below (Init Duration not included):&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%2F9l4dlzhd4vfhls10h6f3.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%2F9l4dlzhd4vfhls10h6f3.png" alt="longestlambda" width="800" height="163"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Average request time was &lt;em&gt;70ms&lt;/em&gt; which means that those warm Lambdas were really hitting it off. While this API is probably not like anything you'd have in production (or is it?), it's still an interesting topic to discuss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;I would say that API in a "fat" Lambda does not have to be terrible. Lambda should definitely not be obese, but a chubby one can perform. This seems to be a good fit for an API up to a certain size and if you don't want to manage or worry about infrastructure. As it grows it can be split into micro-services behind an API Gateway. And if performance is critical, the sweet spot might be scheduling provisioned concurrency after you understand the usage patterns.&lt;/p&gt;

&lt;p&gt;Thanks for reading and feel free to leave your thoughts and experiences in the comments! 👋&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>lambda</category>
      <category>aws</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Deploy a NestJS API to AWS Lambda with Serverless Framework</title>
      <dc:creator>Marko Djakovic</dc:creator>
      <pubDate>Wed, 20 Apr 2022 17:22:39 +0000</pubDate>
      <link>https://dev.to/aws-builders/deploy-a-nestjs-api-to-aws-lambda-with-serverless-framework-4poo</link>
      <guid>https://dev.to/aws-builders/deploy-a-nestjs-api-to-aws-lambda-with-serverless-framework-4poo</guid>
      <description>&lt;p&gt;Have you ever wondered how easy it can be to deploy and host an API? Scalable, stable, a piece of cake to deploy and costs almost nothing. The goal of this article is to demonstrate just that. We will develop a simple API which will be deployed to AWS cloud as a single Lambda function behind an API Gateway - a so-called Mono-Lambda. Whether Lambda "should" be used in that way is a different topic which I'd gladly discuss over beer. 🙂🍺&lt;/p&gt;

&lt;p&gt;By the way, this article is now published on my personal website as well: &lt;a href="https://marko.dj/posts/2022-04-20-deploy-nestjs-api-aws-lambda-serverless/" rel="noopener noreferrer"&gt;marko.dj - Deploy a NestJS API to AWS Lambda with Serverless Framework&lt;/a&gt; - feel free to continue reading there!&lt;/p&gt;

&lt;h2&gt;
  
  
  What to expect from this article?
&lt;/h2&gt;

&lt;p&gt;We will just scratch the surface of NestJS framework and its neat development experience. Once we wire it with Serverless Framework, we'll learn how quickly our API can see the light of day, going from localhost to AWS cloud in just a few steps. To demonstrate this, we will create an API for managing a database of songs - Songs API, and we'll pretend it's not useless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;Songs API will expose endpoints for listing all songs in the database, fetching a single song details, adding and removing songs. Given the requirements, the song model has properties &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;artist&lt;/code&gt;, &lt;code&gt;length&lt;/code&gt; in seconds, &lt;code&gt;genre&lt;/code&gt; and &lt;code&gt;album&lt;/code&gt;. API endpoints could look something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET songs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET songs/:id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST songs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DELETE songs/:id&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nestjs.com/" rel="noopener noreferrer"&gt;NestJS&lt;/a&gt; - a powerful framework for creating sever-side applications&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://typeorm.io/" rel="noopener noreferrer"&gt;TypeORM&lt;/a&gt; - an ORM library for TypeScript, integrates nicely with Nest for database access&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.serverless.com/" rel="noopener noreferrer"&gt;Serverless Framework&lt;/a&gt; - easy to use framework for developing and deploying serverless apps&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/FormidableLabs/serverless-jetpack" rel="noopener noreferrer"&gt;Serverless Jetpack&lt;/a&gt; - a low-config plugin that packages our code to be deployed to AWS Lambda&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/vendia/serverless-express" rel="noopener noreferrer"&gt;Serverless Express&lt;/a&gt; - library that makes our "plain" NestJS API play nicely with Serverless&lt;/li&gt;
&lt;li&gt;AWS managed services like &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;Lambda&lt;/a&gt;, &lt;a href="https://aws.amazon.com/api-gateway/" rel="noopener noreferrer"&gt;API Gateway&lt;/a&gt; and &lt;a href="https://aws.amazon.com/rds/" rel="noopener noreferrer"&gt;RDS&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope it sounds fun and simple enough, so let's dig in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Nest CLI and create a new project and module
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i -g @nestjs/cli
nest new songs-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point the API is already set up - run it using &lt;code&gt;npm run start&lt;/code&gt; and open &lt;code&gt;localhost:3000&lt;/code&gt; to see the hello world response. This is made possible by the &lt;code&gt;main.ts&lt;/code&gt; file that is generated in the project root:&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;NestFactory&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;@nestjs/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppModule&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;./app.module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&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;NestFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AppModule&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're going to create &lt;code&gt;song&lt;/code&gt; module, which will contain the controller, service and entity definitions. Each of these can be created individually, but the Nest CLI provides a useful command to create the module and all the required files in it in one go. It comes in handy when creating REST APIs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nest generate resource song
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Skeleton of the &lt;code&gt;song&lt;/code&gt; module is generated. Next, we have to install dependencies for accessing the database. Since the API will run on top of a MySQL database, the following libraries should be added to the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save @nestjs/typeorm typeorm mysql2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Generating the module skeleton was convenient, but of course our business logic needs to be written. Perhaps we won't be needing all the generated DTOs, we might change or add some paths to the controller, and we need to implement our entity, of course.&lt;/p&gt;

&lt;p&gt;Since we installed TypeORM dependency, let's use it to configure object-relational mapping for the Song entity according to the above specification:&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;Column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PrimaryGeneratedColumn&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;typeorm&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="nd"&gt;Entity&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;Song&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;PrimaryGeneratedColumn&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;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;artist&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="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;duration&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;genre&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="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;album&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make it work now we just need to add import to the module definition:&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;Module&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;@nestjs/common&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;SongService&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;./song.service&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;SongController&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;./song.controller&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;TypeOrmModule&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;@nestjs/typeorm&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;Song&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;./entities/song.entity&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="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TypeOrmModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forFeature&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;Song&lt;/span&gt;&lt;span class="p"&gt;])],&lt;/span&gt;
  &lt;span class="na"&gt;controllers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SongController&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="nx"&gt;SongService&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;class&lt;/span&gt; &lt;span class="nc"&gt;SongModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's implement the service layer. &lt;code&gt;SongService&lt;/code&gt; uses &lt;code&gt;Repository&lt;/code&gt; provided by TypeORM to access the database:&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;Injectable&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;@nestjs/common&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;InjectRepository&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;@nestjs/typeorm&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;Repository&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;typeorm&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;Song&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;./entities/song.entity&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="nd"&gt;Injectable&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;SongService&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;InjectRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Song&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;songRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Repository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Song&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;song&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Song&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Song&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;songRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;song&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Song&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;songRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findOne&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;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Song&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;songRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;remove&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;number&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;songRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&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;For simplicity, I'll re-use the entity as a DTO, so we can remove the whole &lt;code&gt;dto&lt;/code&gt; folder that was generated. Then our controller and service will be rewritten to look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Controller&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="nx"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ParseIntPipe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&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;SongService&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;./song.service&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;Song&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;./entities/song.entity&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="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;songs&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;SongController&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;songService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SongService&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="nd"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;song&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Song&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Song&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;songService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;song&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="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Song&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;songService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&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="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:id&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="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ParseIntPipe&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;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Song&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;songService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:id&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="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ParseIntPipe&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;number&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;songService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a general rule, it's always better to de-couple DTO and entity classes and have some sort of object mapper.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database
&lt;/h3&gt;

&lt;p&gt;Database is mentioned quite a few times, but where is it? 🤔&lt;/p&gt;

&lt;p&gt;Firstly, let's test our code against a local MySQL database. Once you connect to local server, execute the following init script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="nv"&gt;`songsapi`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="nv"&gt;`songsapi`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`song`&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;`id`&lt;/span&gt;       &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;`name`&lt;/span&gt;     &lt;span class="nb"&gt;varchar&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="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;`artist`&lt;/span&gt;   &lt;span class="nb"&gt;varchar&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="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;`duration`&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;`genre`&lt;/span&gt;    &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;`album`&lt;/span&gt;    &lt;span class="nb"&gt;varchar&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="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`id`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;InnoDB&lt;/span&gt;
  &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;CHARSET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that make sure the API can connect to it by adding the following configuration to &lt;code&gt;app.module.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Module&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&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;AppController&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;./app.controller&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;AppService&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;./app.service&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;TypeOrmModule&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;@nestjs/typeorm&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;SongModule&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;./song/song.module&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="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;TypeOrmModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRoot&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;mysql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3306&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;songsapi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;autoLoadEntities&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="nx"&gt;SongModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;controllers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AppController&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="nx"&gt;AppService&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;class&lt;/span&gt; &lt;span class="nc"&gt;AppModule&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;Feel free to hardcode the values above to those corresponding to your local database configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it 🚀
&lt;/h2&gt;

&lt;p&gt;Type &lt;code&gt;npm run start&lt;/code&gt; in the terminal and in a few seconds it should be up and running. Test it by sending some requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'localhost:3000/songs'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data-raw&lt;/span&gt; &lt;span class="s1"&gt;'{
    "name": "In corpore sano",
    "artist": "Konstrakta",
    "duration": 182,
    "album": "In corpore sano",
    "genre": "pop"
}'&lt;/span&gt;
&lt;span class="c"&gt;# Response:&lt;/span&gt;
&lt;span class="c"&gt;# {"name":"In corpore sano","artist":"Konstrakta","duration":182,"album":"In corpore sano","genre":"pop","id":1}&lt;/span&gt;

&lt;span class="c"&gt;# Get a single song by id&lt;/span&gt;
curl &lt;span class="s1"&gt;'localhost:3000/songs/1'&lt;/span&gt;
&lt;span class="c"&gt;# Response:&lt;/span&gt;
&lt;span class="c"&gt;# {"name":"In corpore sano","artist":"Konstrakta","duration":182,"album":"In corpore sano","genre":"pop","id":1}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works! Now that we've tested our API locally, it's time to deploy it to the cloud and make it available to the world!&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving to the cloud 🌥
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;NOTE 1: It is assumed that you already have an AWS account, so creating one will not be covered.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE 2: Make sure you have enough privileges to follow the steps. In case of IAM user, the shortcut is to have &lt;code&gt;arn:aws:iam::aws:policy/AdministratorAccess&lt;/code&gt; managed policy attached.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure AWS account credentials
&lt;/h3&gt;

&lt;p&gt;Add a profile to your AWS credentials file (usually ~/.aws/credentials):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
[profile-name]
region=your_region
aws_access_key_id=xxx
aws_secret_access_key=yyy
aws_session_token=... (if applicable)
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that an environment variable should be set to activate the profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;profile-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should be ready to interact with your AWS cloud, feel free to quickly test if it's setup correctly by listing all S3 buckets for example:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aws s3 ls&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Spin up a free-tier RDS database
&lt;/h3&gt;

&lt;p&gt;So far we have successfully tested the API with local MySQL database, but now we need one on AWS. It can be done manually through the AWS Console, or you can execute the CloudFormation template provided here.&lt;/p&gt;

&lt;p&gt;*WARNING: Please be informed about the pricing and free-tier eligibility of your account. All new AWS customers should get 1 year of free tier for certain services. Otherwise you might incur some costs as described in the official AWS RDS pricing guide -&amp;gt; &lt;a href="https://aws.amazon.com/rds/mysql/pricing" rel="noopener noreferrer"&gt;https://aws.amazon.com/rds/mysql/pricing&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;AWSTemplateFormatVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2010-09-09'&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;SongsDatabase&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::RDS::DBInstance&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;AllocatedStorage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
      &lt;span class="na"&gt;DBInstanceClass&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db.t3.micro&lt;/span&gt;
      &lt;span class="na"&gt;DBInstanceIdentifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;songs-database&lt;/span&gt;
      &lt;span class="na"&gt;PubliclyAccessible&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;StorageType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gp2&lt;/span&gt;
      &lt;span class="na"&gt;MasterUsername&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xxx&lt;/span&gt; &lt;span class="c1"&gt;# change&lt;/span&gt;
      &lt;span class="na"&gt;MasterUserPassword&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yyy&lt;/span&gt; &lt;span class="c1"&gt;# change&lt;/span&gt;
      &lt;span class="na"&gt;Engine&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql&lt;/span&gt;
      &lt;span class="na"&gt;EngineVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;8.0.28&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file above as rds.yaml for example and run it using AWS CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation deploy &lt;span class="nt"&gt;--stack-name&lt;/span&gt; songs-api-db &lt;span class="nt"&gt;--template-file&lt;/span&gt; rds.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a few minutes the database will be ready.&lt;/p&gt;

&lt;p&gt;Obtain the database URL either through AWS Console by navigating to RDS, or by listing exports of CloudFormation using the following command &lt;code&gt;aws cloudformation list-exports&lt;/code&gt;. Connect to it and execute the database init script as it was done for the local instance.&lt;/p&gt;

&lt;p&gt;Now that our database is running in the cloud, it's time to reconfigure our app to work with the RDS database instead of local one - so don't forget to update the relevant details like url, password and the rest in &lt;code&gt;app.module.ts&lt;/code&gt; file. After that it's ready to be deployed, which is covered in the next ste.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install and configure Serverless Framework
&lt;/h3&gt;

&lt;p&gt;Install Serverless Framework CLI:&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; &lt;span class="nt"&gt;-g&lt;/span&gt; serverless
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the root of the project, we should create the &lt;code&gt;serverless.yaml&lt;/code&gt; file which describes the deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;songs-api&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;3'&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-jetpack&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;nodejs14.x&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-central-1&lt;/span&gt; &lt;span class="c1"&gt;# or whatever your region is&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;api&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;dist/lambda.handler&lt;/span&gt;
    &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;any&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/{proxy+}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this configuration, the API Gateway will just proxy every request to the Lambda function and our NestJS app will handle it. The &lt;code&gt;handler&lt;/code&gt; value is a file that contains the entry point for our app and will be explained in a minute.&lt;/p&gt;

&lt;p&gt;Notice the &lt;code&gt;serverless-jetpack&lt;/code&gt; plugin - it takes care of packaging our app very efficiently for Serverless. There are other plugins for this, but I've discovered this one recently and it's a lot faster than others I've used so far. Read more about it on &lt;a href="https://github.com/FormidableLabs/serverless-jetpack" rel="noopener noreferrer"&gt;its official github page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Install it as a dev dependency using npm:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Now there's one more step before we can deploy our API - Serverless Express library to make it work in Lambda environment and it concerns the function handler.&lt;/p&gt;

&lt;h3&gt;
  
  
  Serverless Express
&lt;/h3&gt;

&lt;p&gt;Install &lt;code&gt;serverless-express&lt;/code&gt; library that bootstraps Express based apps to work with Lambda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @vendia/serverless-express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in the source folder create a &lt;code&gt;lambda.ts&lt;/code&gt; file that contains the Lambda handler function, which is the entry point, as referenced in the above &lt;code&gt;serverless.yaml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;serverlessExpress&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;@vendia/serverless-express&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;NestFactory&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;@nestjs/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppModule&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;./app.module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cachedServer&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;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cachedServer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nestApp&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;NestFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AppModule&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;nestApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;cachedServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;serverlessExpress&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nestApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHttpAdapter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getInstance&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="nf"&gt;cachedServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build, deploy &amp;amp; test 🚀
&lt;/h2&gt;

&lt;p&gt;Finally, we are going to deploy our API to the cloud. It's fairly simple, first it should be built:&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 build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... and then deployed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;serverless deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shortly, you'll get an auto-generated url which you can use to hit the API so feel free to test it by adding, listing  and removing songs. You can see logs and monitor how your app performs in the built-in dashboards on Lambda &amp;amp; &lt;a href="https://aws.amazon.com/cloudwatch/" rel="noopener noreferrer"&gt;CloudWatch&lt;/a&gt; services on AWS Management Console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean-up
&lt;/h2&gt;

&lt;p&gt;After you've played around a bit with your API, it's time to clean-up all the resources you created on your AWS cloud. If you followed the steps exactly, you'll have two CloudFormation stacks deployed - one for the database and the other for the Serverless deployment. You can either remove them manually via the Console or by running the following CLI commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;serverless remove
aws cloudformation delete-stack &lt;span class="nt"&gt;--stack-name&lt;/span&gt; songs-api-db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion &amp;amp; final thoughts
&lt;/h2&gt;

&lt;p&gt;I hope you made it this far and that I didn't bore you too much. Even though the main focus was on Serverless deployment on AWS Lambda, this article covered a few things along the way like setting up a simple NestJS project with TypeORM and creating an RDS MySQL database instance on AWS via CloudFormation.&lt;/p&gt;

&lt;p&gt;What would be great for this kind of API to scale better is configuring an &lt;a href="https://aws.amazon.com/rds/proxy/" rel="noopener noreferrer"&gt;RDS Proxy&lt;/a&gt; on top of the database. Also, adding user authentication by using AWS Cognito is something which would fit nicely into this setup. Very recently AWS announced &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html" rel="noopener noreferrer"&gt;Lambda function URL&lt;/a&gt; feature, which eliminates the need for API Gateway but has other trade-offs, which I plan to explore next.&lt;/p&gt;

&lt;p&gt;There are definitely some security aspects worth discussing for this to become production-ready, but it is beyond the scope of this article.&lt;/p&gt;

&lt;p&gt;Thanks for reading and if you have any questions or suggestions feel free to comment!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;There is a follow-up post on this, check it out - &lt;a href="https://marko.dj/posts/2022-07-08-aws-lambda-cold-starts-nestjs/" rel="noopener noreferrer"&gt;AWS Lambda Cold Starts: The Case of a NestJS Mono-Lambda API&lt;/a&gt;&lt;/em&gt; 🙂&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>nestjs</category>
      <category>lambda</category>
    </item>
  </channel>
</rss>
