<?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: Roman Abdulmanov</title>
    <description>The latest articles on DEV Community by Roman Abdulmanov (@roman_abdulmanov).</description>
    <link>https://dev.to/roman_abdulmanov</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%2F927873%2F141d479a-4f19-4ffe-88ec-a694ab74a086.jpeg</url>
      <title>DEV Community: Roman Abdulmanov</title>
      <link>https://dev.to/roman_abdulmanov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/roman_abdulmanov"/>
    <language>en</language>
    <item>
      <title>10 Tips for Those Who Decide to Start a New Project on AWS</title>
      <dc:creator>Roman Abdulmanov</dc:creator>
      <pubDate>Wed, 01 Mar 2023 08:18:37 +0000</pubDate>
      <link>https://dev.to/roman_abdulmanov/10-tips-if-you-decide-to-start-a-project-on-aws-3ebd</link>
      <guid>https://dev.to/roman_abdulmanov/10-tips-if-you-decide-to-start-a-project-on-aws-3ebd</guid>
      <description>&lt;p&gt;&lt;em&gt;Here I collected few tips that may be useful for those just at the beginning of their AWS journey.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Account separation
&lt;/h4&gt;

&lt;p&gt;It is worth separating production and dev accounts from the very beginning. It costs almost nothing, but if you do not do it initially, you will have to spend precious time on rework.&lt;/p&gt;

&lt;p&gt;This is important to do for the following reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It allows you to limit people accessing the production environment&lt;/li&gt;
&lt;li&gt;It allows you to create a loosely coupled architecture where the prod environment doesn't depend on the testing environment&lt;/li&gt;
&lt;li&gt;You will have separate billing so that you will have the ability to see how much you pay for your production &lt;/li&gt;
&lt;li&gt;This is necessary for passing different security and compliance checks&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  2. Permissions
&lt;/h4&gt;

&lt;p&gt;The question may quickly arise: how to limit the permissions for developers? For example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Protect against admins assuming roles between accounts. Developers with administrator rights could assume a role from another account by default&lt;/li&gt;
&lt;li&gt;If you have not restricted access to SSM, decrypt methods, etc., developers can access various secrets stored in the environment (tokens, keys, etc.)&lt;/li&gt;
&lt;li&gt;Access to the Billing Dashboard&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The simple solution: grant only the necessary permissions, but this is more complex than it looks because the scope of rights changes constantly and needs to be continuously reviewed. With this approach, developers will periodically lose access when they need to develop something new that was not considered earlier.&lt;/p&gt;

&lt;p&gt;To solve this, you can grant a more comprehensive range of rights, e.g., up to an Administrator on a test account. But set different &lt;strong&gt;permission boundaries&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/mvandongen/prevent-privilege-escalation-with-iam-permissions-boundary-a-practical-guide-40nc"&gt;Good post about it.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I also recommend setting up &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa_enable_virtual.html#enable-virt-mfa-for-root" rel="noopener noreferrer"&gt;MFA&lt;/a&gt; for all accounts to reduce the risk of token leaks.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Automation
&lt;/h4&gt;

&lt;p&gt;AWS is very captivating with its simplicity in setting something up manually, especially at the beginning of a project.&lt;/p&gt;

&lt;p&gt;But behind this simplicity lies many complexities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There is a very high probability of breaking something by accident&lt;/li&gt;
&lt;li&gt;It is difficult to repeat the environment and, for example, create an identical staging for the developer&lt;/li&gt;
&lt;li&gt;It isn't easy to understand how our system looks like in general because this knowledge is in our heads&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To solve this, I suggest using one of the popular solutions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/" rel="noopener noreferrer"&gt;Pulumi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.serverless.com/" rel="noopener noreferrer"&gt;Serverless Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/serverless/sam/" rel="noopener noreferrer"&gt;AWS SAM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/cloudformation/?nc1=h_ls" rel="noopener noreferrer"&gt;AWS CloudFormation &lt;/a&gt; &lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  4. CloudFormation
&lt;/h4&gt;

&lt;p&gt;The 3 solutions above use CF. So if using CF to manage infrastructure, you should familiarize yourself with its &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html" rel="noopener noreferrer"&gt;limitations&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The most frustrating thing is the &lt;strong&gt;500&lt;/strong&gt; resource limit, which you will run out of very quickly if you prefer a serverless approach or have a large project.&lt;/p&gt;

&lt;p&gt;So, to avoid this problem at a crucial moment, I suggest you think about a strategy for placing resources in nested stacks (e.g. &lt;a href="https://github.com/dougmoscrop/serverless-plugin-split-stacks" rel="noopener noreferrer"&gt;plugin&lt;/a&gt; for Serverless Framework).&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Cognito
&lt;/h4&gt;

&lt;p&gt;AWS Cognito is a powerful service for a customer identity and access management.&lt;/p&gt;

&lt;p&gt;But when setting it up, you can make two mistakes that can cause serious trouble in production:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-case-sensitivity.html" rel="noopener noreferrer"&gt;Case-sensitive user pool&lt;/a&gt;. If you create a User Pool programmatically, you must set the &lt;strong&gt;CaseSensitive&lt;/strong&gt; parameter to false; otherwise, it will default to true. This complicates the sign-in process (e.g., &lt;a href="mailto:User@test.co"&gt;User@test.co&lt;/a&gt; vs &lt;a href="mailto:user@test.co"&gt;user@test.co&lt;/a&gt;). And most importantly, after creating the User Pool, you can no longer change this setting&lt;/li&gt;
&lt;li&gt;The same applies to &lt;strong&gt;Mutable&lt;/strong&gt; attributes. This means that if, for example, you mark an email as Mutable, users will not be able to change their emails, and most importantly, you cannot change the value of Mutable without recreating the User Pool&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Recreating the User Pool is only possible by resetting user passwords, which would look suspicious. Therefore, if you made such mistakes when configuring your User Pool and have already launched the product, you may have unexpected problems that could have been easily avoided initially.&lt;/p&gt;

&lt;h4&gt;
  
  
  6. DynamoDB
&lt;/h4&gt;

&lt;p&gt;DynamoDB, unlike the usual relational databases, recommends using the &lt;a href="https://aws.amazon.com/blogs/compute/creating-a-single-table-design-with-amazon-dynamodb/" rel="noopener noreferrer"&gt;single table design&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For Node.js users to make this approach easier to understand and make the code incredibly simpler, I recommend looking at the &lt;a href="https://dev.to/sensedeep/dynamodb-onetable-5735"&gt;OneTable&lt;/a&gt; library.&lt;/p&gt;

&lt;p&gt;Here are some benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Single-table design&lt;/li&gt;
&lt;li&gt;TypeScript support&lt;/li&gt;
&lt;li&gt;Local unit test support&lt;/li&gt;
&lt;li&gt;Simple API&lt;/li&gt;
&lt;li&gt;Migrations support&lt;/li&gt;
&lt;li&gt;Indexes &amp;amp; conditions support&lt;/li&gt;
&lt;li&gt;Built-in encryption support&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  7. Backups
&lt;/h4&gt;

&lt;p&gt;AWS has a built-in &lt;a href="https://aws.amazon.com/backup/" rel="noopener noreferrer"&gt;backup service&lt;/a&gt;. This is a very powerful service that, for example, allows you to create a particular type of backup without the possibility of deleting previously created snapshots.&lt;/p&gt;

&lt;p&gt;But this service has its limitations. We are faced with the fact that when restoring DynamoDB, we must manually restore DDB Streams. This would be problematic if, as we do, everything is deployed through CloudFormation, which does not "like" manual changes.&lt;/p&gt;

&lt;p&gt;Therefore, to avoid surprises at the most inconvenient moment, in addition to creating backups, you need to develop, test, and &lt;strong&gt;document&lt;/strong&gt; the recovery procedure and periodically perform test recovery to ensure that the process is still up to date.&lt;/p&gt;

&lt;h4&gt;
  
  
  8. Lambda
&lt;/h4&gt;

&lt;p&gt;If you are using lambda, you probably have heard about the &lt;a href="https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-1/" rel="noopener noreferrer"&gt;cold start problem&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To solve this, there are different workarounds: &lt;a href="https://acloudguru.com/blog/engineering/how-to-keep-your-lambda-functions-warm" rel="noopener noreferrer"&gt;warm-up requests&lt;/a&gt;, language-specific &lt;a href="https://aws.amazon.com/blogs/compute/reducing-java-cold-starts-on-aws-lambda-functions-with-snapstart/" rel="noopener noreferrer"&gt;solutions&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/provisioned-concurrency.html" rel="noopener noreferrer"&gt;lambda provisioned concurrency&lt;/a&gt;, etc.&lt;/p&gt;

&lt;p&gt;But I want to focus on one, in my opinion, the most critical factor, in addition to the chosen language &amp;amp; &lt;a href="https://docs.aws.amazon.com/lambda/latest/operatorguide/computing-power.html" rel="noopener noreferrer"&gt;memory&lt;/a&gt;: function bundle size.&lt;/p&gt;

&lt;p&gt;Your bundle size &lt;a href="https://lumigo.io/blog/this-is-all-you-need-to-know-about-lambda-cold-starts/" rel="noopener noreferrer"&gt;directly affects&lt;/a&gt; the cold start time because AWS needs to prepare the code before it can be run. Even if you use Lambda Layers and extract dependencies, these Layers still need to be loaded.&lt;/p&gt;

&lt;p&gt;So I advise trying to minimize the bundle size in every possible way. For example, we use Node.js, we never add AWS SDK because it is already present in the environment, we use esbuild because it showed the best results in terms of speed and size, and as a result, we don’t have a single function larger than 500KBs.&lt;/p&gt;

&lt;h4&gt;
  
  
  9. Alarms
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html" rel="noopener noreferrer"&gt;The alarm system&lt;/a&gt; is incredibly useful for detecting suspicious activity and important alerts.&lt;/p&gt;

&lt;p&gt;For example, we set up alarms when various errors occur in the system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Lambda errors &amp;amp; throttling&lt;/li&gt;
&lt;li&gt;Errors in logs (e.g. from docker)&lt;/li&gt;
&lt;li&gt;WAF alarms&lt;/li&gt;
&lt;li&gt;Billing alarms (spending more than a certain amount in $)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/create-alarms-for-custom-metrics-using-amazon-cloudwatch-anomaly-detection.html" rel="noopener noreferrer"&gt;Custom metrics&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And then, we connected SNS to send these alarms to us in a special slack channel. &lt;/p&gt;

&lt;h4&gt;
  
  
  10. Audit logs
&lt;/h4&gt;

&lt;p&gt;You can enable activity logging via &lt;a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-getting-started.html" rel="noopener noreferrer"&gt;CloudTrail&lt;/a&gt;. It costs almost nothing but can be indispensable when analyzing suspicious activity or determining why something is not working in the system.&lt;/p&gt;

&lt;p&gt;We had an incident with user registration in Cognito, and this logging helped to sort out the real reasons.&lt;/p&gt;

</description>
      <category>react</category>
      <category>vscode</category>
      <category>debugging</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Production-Ready AWS CloudFront for SPA</title>
      <dc:creator>Roman Abdulmanov</dc:creator>
      <pubDate>Fri, 03 Feb 2023 15:19:09 +0000</pubDate>
      <link>https://dev.to/roman_abdulmanov/production-ready-aws-cloudfront-for-spa-2gm3</link>
      <guid>https://dev.to/roman_abdulmanov/production-ready-aws-cloudfront-for-spa-2gm3</guid>
      <description>&lt;p&gt;In this article, we will discuss the basic scenario for using &lt;strong&gt;&lt;a href="https://aws.amazon.com/cloudfront/?nc1=h_ls" rel="noopener noreferrer"&gt;CloudFront&lt;/a&gt;&lt;/strong&gt; to serve a &lt;strong&gt;static SPA&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;As always, we will try to make it not just work but to achieve the maximum performance available in AWS. So, I'd like to start with a basic scenario and then try to improve it.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Static files
&lt;/h2&gt;

&lt;p&gt;If you work in the &lt;strong&gt;AWS&lt;/strong&gt; environment, you usually upload static files to &lt;strong&gt;S3&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You need to configure &lt;strong&gt;Bucket Policy&lt;/strong&gt; to enable public access (without authorization):&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="pi"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&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;2012-10-17"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Statement"&lt;/span&gt;&lt;span class="pi"&gt;:&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;Sid"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PublicReadGetObject"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Effect"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Allow"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Principal"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&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;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;s3:GetObject"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Resource"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:s3:::my-website-bucket-static/*"&lt;/span&gt;
        &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you need to enable &lt;strong&gt;Static website hosting&lt;/strong&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%2Fhcflsjh4vwgn6ab6tc2k.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%2Fhcflsjh4vwgn6ab6tc2k.png" alt="Image description" width="800" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's it! Now your site is available by the url: &lt;br&gt;
&lt;code&gt;http://&amp;lt;bucket-name&amp;gt;.s3-website-&amp;lt;region&amp;gt;.amazonaws.com/&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  HTTPS
&lt;/h2&gt;

&lt;p&gt;The apparent drawback of the current solution is the lack of &lt;strong&gt;HTTPS&lt;/strong&gt;. So let's set up &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html" rel="noopener noreferrer"&gt;&lt;strong&gt;CloudFront&lt;/strong&gt;&lt;/a&gt; to resolve this.&lt;/p&gt;

&lt;p&gt;Technically we only need to specify the correct &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html" rel="noopener noreferrer"&gt;CloudFront &lt;strong&gt;Origin&lt;/strong&gt;&lt;/a&gt;:&lt;/p&gt;

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

&lt;p&gt;When the distribution is deployed, your site will be available by CloudFront &lt;a href="https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-cloudfront-distribution.html" rel="noopener noreferrer"&gt;DNS&lt;/a&gt; name, e.g.: &lt;br&gt;
&lt;code&gt;https://21nnj7ewt8yqwc.cloudfront.net&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The most important advantage of using CF is that you will get a full-fledged CDN, i.e., your static files will be delivered from the location closest to you, which means that the delays in the website loading will be minimal.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  SPA
&lt;/h2&gt;

&lt;p&gt;You will soon notice that if you have SPA, routing will not work. E.g.:&lt;br&gt;
&lt;code&gt;https://21nnj7ewt8yqwc.cloudfront.net/test/route&lt;/code&gt;&lt;br&gt;
Because CloudFront will try to redirect requests to the &lt;code&gt;test/route&lt;/code&gt; folder, which does not exist in S3.&lt;/p&gt;
&lt;h2&gt;
  
  
  SPA: First attempt to fix
&lt;/h2&gt;

&lt;p&gt;In some posts or answers, you may find the following solution: configure &lt;strong&gt;Error document&lt;/strong&gt; for S3 Static website hosting to redirect failed attempts back to index.html:&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%2Fxmaocf01ds5j6xcp0ufc.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%2Fxmaocf01ds5j6xcp0ufc.png" alt="Image description" width="800" height="643"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It will work, but you will have below issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;All requests will have a &lt;strong&gt;404&lt;/strong&gt; error code
&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%2Fn8y7mder9anyk4ibb0sf.png" alt="Image description" width="538" height="138"&gt;
&lt;/li&gt;
&lt;li&gt;They will be served not from the cache (CF will request content on each request from S3)
&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%2Fy46wida5k9b7kugpi2pu.png" alt="Image description" width="424" height="54"&gt;
&lt;/li&gt;
&lt;li&gt;S3 will request the content twice, first at the path specified in the request, and after an error, index.html itself&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  SPA: Second attempt to fix
&lt;/h2&gt;

&lt;p&gt;The most popular solution that you could find on the internet: configure CloudFront with &lt;strong&gt;Custom Error Response&lt;/strong&gt;. In our case, it would be:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffwd1d361a8kxq6ggbzki.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%2Ffwd1d361a8kxq6ggbzki.png" alt="Image description" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And it will fix the issue with the &lt;strong&gt;404&lt;/strong&gt; error code:&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%2Fkwcfryp7oon61rscg7sv.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%2Fkwcfryp7oon61rscg7sv.png" alt="Image description" width="800" height="93"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But unfortunately, you will still have the issue with the cache and two requests. The reason for this is simple: CloudFront will request the page from S3, and when it receives a 404, it will return index.html from the cache. So you will lose all the advantages of &lt;strong&gt;CDN&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  SPA: Solution
&lt;/h2&gt;

&lt;p&gt;To fix both issues, we can implement &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html" rel="noopener noreferrer"&gt;CloudFront Function&lt;/a&gt; and fix routing for our SPA.&lt;/p&gt;

&lt;p&gt;Just create a new Function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&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="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/index.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; 
  &lt;span class="k"&gt;else&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;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/index.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;request&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 attach it to the CloudFront:&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%2F6vg57bvdlu1i5ae4q8li.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%2F6vg57bvdlu1i5ae4q8li.png" alt="Image description" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you will have the correct response code, and all requests will be served from the cache:&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%2F7jiiy91hvqs5o3vdsabv.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%2F7jiiy91hvqs5o3vdsabv.png" alt="Image description" width="390" height="42"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are no penalties for launching these functions. They run in the exact location where the CDN runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  S3 cache policy
&lt;/h2&gt;

&lt;p&gt;You can configure some cache parameters on the S3 level. But you should do this very carefully. For example, if you take example from popular &lt;a href="https://github.com/k1LoW/serverless-s3-sync" rel="noopener noreferrer"&gt;S3 sync plugin&lt;/a&gt; for &lt;strong&gt;Serverless Framework&lt;/strong&gt; and upload &lt;strong&gt;index.html&lt;/strong&gt; with below 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="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;index.html&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CacheControl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;no-cache'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will force CloudFront to request this date on each request:&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%2F5afsc48vdi2yv1swp60e.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%2F5afsc48vdi2yv1swp60e.png" alt="Image description" width="476" height="38"&gt;&lt;/a&gt;&lt;br&gt;
Which, firstly, will work slowly, and secondly, it will load Origin with unnecessary requests.&lt;/p&gt;

&lt;p&gt;The disadvantage of not having this setting is that you must make an invalidation request in the AWS console or via CLI / API to update the cached content.&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%2F1bd5t8l6rssqglms5fer.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%2F1bd5t8l6rssqglms5fer.png" alt="Image description" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  API
&lt;/h1&gt;

&lt;p&gt;If you have an API inside AWS infrastructure, please read my previous posts about &lt;a href="https://dev.to/roman_abdulmanov/improve-aws-cloudfront-api-performance-with-origin-shield-4al6"&gt;Origin Shield&lt;/a&gt; and &lt;a href="https://dev.to/roman_abdulmanov/production-ready-graphql-for-aws-serverless-54be"&gt;GraphQL&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;By combining these approaches, you will achieve the best performance results and a higher level of security.&lt;/p&gt;

&lt;p&gt;Thanks!&lt;/p&gt;

</description>
      <category>crypto</category>
      <category>ethereum</category>
      <category>web3</category>
    </item>
    <item>
      <title>Improve AWS CloudFront API performance with Origin Shield</title>
      <dc:creator>Roman Abdulmanov</dc:creator>
      <pubDate>Mon, 31 Oct 2022 18:01:38 +0000</pubDate>
      <link>https://dev.to/roman_abdulmanov/improve-aws-cloudfront-api-performance-with-origin-shield-4al6</link>
      <guid>https://dev.to/roman_abdulmanov/improve-aws-cloudfront-api-performance-with-origin-shield-4al6</guid>
      <description>&lt;p&gt;This post will be helpful for those who use &lt;strong&gt;CloudFront&lt;/strong&gt; with some API (API Gateway, AppSync, custom endpoint) hosted in a single region.&lt;/p&gt;

&lt;p&gt;In this scenario, the client first interacts with CloudFront in the closest region, and then traffic through the regular network reaches the region where the API is located:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PrszuEht--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8hbhclfzseprd2ejghom.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PrszuEht--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8hbhclfzseprd2ejghom.png" alt="Image description" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This may take time if you are far from another network. &lt;/p&gt;

&lt;p&gt;The most reliable approach is to make a multi-regional API, i.e., host your API in different regions. But if for some reason you can't do that yet, there's a trick that can make things a little better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Origin Shield
&lt;/h2&gt;

&lt;p&gt;From AWS documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CloudFront Origin Shield adds an additional layer in the CloudFront caching infrastructure that helps to minimize your origin’s load, improve its availability, and reduce its operating costs. By enabling CloudFront Origin Shield, you get the following benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Better cache hit ratio – all requests from all of CloudFront’s caching layers to your origin go through Origin Shield, increasing the likelihood of a cache hit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reduced origin load – Origin Shield consolidates content requests for the same object to reduce the number of simultaneous requests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Better network performance – When you enable Origin Shield in the AWS Region that has the lowest latency to your origin, you can get better network performance.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;The last paragraph is what interests us. From the description, it follows that it should now work like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MekYjhze--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m5ax8urlitjy3t2lqfx8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MekYjhze--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m5ax8urlitjy3t2lqfx8.png" alt="Image description" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;You can enable Origin Shield through the &lt;strong&gt;AWS Console&lt;/strong&gt; in your Origin settings + choose the region closest to your API:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CeCQCG6U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2nqs097gj0qc2c7kwi5z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CeCQCG6U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2nqs097gj0qc2c7kwi5z.png" alt="Image description" width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We use the following template for &lt;strong&gt;Serverless Framework&lt;/strong&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;Origins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YourID&lt;/span&gt;
    &lt;span class="na"&gt;OriginShield&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;OriginShieldRegion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${aws:region}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;CloudFormation &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-originshield.html"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;I configured two different CloudFronts to use the same API, but in one case with Origin Shield, and the other without&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without&lt;/strong&gt; Origin Shield (1 thread * 100 requests):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wEy6T-Ic--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jjqw8v7mrghvmp29ktpr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wEy6T-Ic--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jjqw8v7mrghvmp29ktpr.png" alt="Image description" width="800" height="64"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With&lt;/strong&gt; Origin Shield:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aoK2po7_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nashes8j5f0ip8960517.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aoK2po7_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nashes8j5f0ip8960517.png" alt="Image description" width="800" height="50"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without&lt;/strong&gt; Origin Shield (4 thread * 50 requests):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y_GIOHq3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bibf1g709rv8cs018cqj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y_GIOHq3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bibf1g709rv8cs018cqj.png" alt="Image description" width="800" height="64"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With&lt;/strong&gt; Origin Shield:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T_6TmOke--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/witjmarcemx0mzejzgkg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T_6TmOke--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/witjmarcemx0mzejzgkg.png" alt="Image description" width="800" height="56"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we can see in this use case, the Origin Shield solution is a bit faster!&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Using Origin Shield is not free. Check &lt;a href="https://aws.amazon.com/cloudfront/pricing"&gt;pricing&lt;/a&gt; before usage:
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ygDk84US--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sld7lzv6pmk6algaicih.png" alt="Image description" width="800" height="194"&gt;
&lt;/li&gt;
&lt;li&gt;Synthetic tests say nothing about how they will work in your conditions, so do not believe blindly, but test with your data first.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thanks! &lt;/p&gt;

</description>
      <category>aws</category>
      <category>api</category>
      <category>cloudfront</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Production Ready GraphQL for AWS &amp; Serverless</title>
      <dc:creator>Roman Abdulmanov</dc:creator>
      <pubDate>Fri, 23 Sep 2022 14:06:58 +0000</pubDate>
      <link>https://dev.to/roman_abdulmanov/production-ready-graphql-for-aws-serverless-54be</link>
      <guid>https://dev.to/roman_abdulmanov/production-ready-graphql-for-aws-serverless-54be</guid>
      <description>&lt;h2&gt;
  
  
  What is this post about?
&lt;/h2&gt;

&lt;p&gt;Many articles have been written on implementing a serverless GraphQL API using AWS. But there is almost nothing about how to make it production ready for websites.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article will also be valid for API Gateway.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;You can use different tools: AWS SAM, Serverless Framework, Pulumi, Terraform, pure AWS CDK, or something else. Ultimately, it's not all that important. The main thing is that you do it not manually. Everything must be &lt;strong&gt;automated&lt;/strong&gt; for repeatability. Otherwise, you won't be able to build a reliable CI/CD process.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I like this &lt;a href="https://www.serverless.com/plugins/serverless-appsync-plugin" rel="noopener noreferrer"&gt;plugin&lt;/a&gt; that will make your life easier. It will help you set up AppSync, manage API keys, configure WAF, etc.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Typical architecture
&lt;/h2&gt;

&lt;p&gt;Most articles end up with a below architecture, service or website that directly calls the AppSync API:&lt;/p&gt;

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

&lt;p&gt;The downside of this solution is that the API is susceptible to DDoS attacks, which can significantly increase your monthly bills.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improved architecture
&lt;/h2&gt;

&lt;p&gt;The obvious solution is to add a firewall with request limiting:&lt;/p&gt;

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

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

&lt;p&gt;It will also help if you want to block GraphQL introspection:&lt;/p&gt;

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

&lt;p&gt;And this could be the end, but this solution has one unpleasant flaw for websites: &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request" rel="noopener noreferrer"&gt;preflight request&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preflight request
&lt;/h2&gt;

&lt;p&gt;Since your site and API are on different domains, the browser will make 1 additional request to the server BEFORE any other requests.&lt;/p&gt;

&lt;p&gt;This may come as an unexpected surprise, since the initial request can take almost twice as long.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

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

&lt;p&gt;Details (additional 456 milliseconds to wait for the request to complete): &lt;/p&gt;

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

&lt;p&gt;As I already wrote, since your domain is hosted on &lt;code&gt;mydomain.com&lt;/code&gt;, and the API is on &lt;code&gt;https://[ID].appsync-api.[REGION].amazonaws.com&lt;/code&gt;, the browser will attempt to do additional validation for CORS by sending this request before the first call to the domain hosting the API.&lt;/p&gt;

&lt;p&gt;The browser caches this request for a while, but if this is a public page and many people visit this site for the first time, then they will experience this problem.&lt;/p&gt;

&lt;p&gt;It also starts repeating after some time when the cache expires.&lt;/p&gt;

&lt;h2&gt;
  
  
  Potential solution №1
&lt;/h2&gt;

&lt;p&gt;The first thing that comes to mind if you are unfamiliar with preflight requests is that they can probably be disabled if the backend sets some header.&lt;/p&gt;

&lt;p&gt;Unfortunately this is not possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Potential solution №2
&lt;/h2&gt;

&lt;p&gt;Maybe, I can use a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests" rel="noopener noreferrer"&gt;simple request&lt;/a&gt; to workaround this restriction? &lt;/p&gt;

&lt;p&gt;This will not work if you have non-standard headers (e.g. Authorization), you will also have to set the wrong content-type. Even if it works, this does not look like a production ready solution and can be blocked by AWS / browsers anytime. &lt;/p&gt;

&lt;h2&gt;
  
  
  Potential solution №3
&lt;/h2&gt;

&lt;p&gt;You can try to use &lt;a href="https://stackoverflow.com/a/31555285/1235413" rel="noopener noreferrer"&gt;iframe technique&lt;/a&gt; but it will not work for non-subdomain urls (we will talk about this later), and it will make the loading even slower because instead of a very fast request, you will have slow iframe loading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Potential solution №4
&lt;/h2&gt;

&lt;p&gt;Can I use &lt;a href="https://aws.amazon.com/ru/blogs/mobile/introducing-custom-domain-names-for-aws-appsync-apis/" rel="noopener noreferrer"&gt;custom domains&lt;/a&gt;? &lt;/p&gt;

&lt;p&gt;Unfortunately, this is not possible too. You can't use the same domain for API (e.g. just &lt;code&gt;mydomain.com&lt;/code&gt;). It will work only with sub-domain (e.g. &lt;code&gt;api.mydomain.com&lt;/code&gt;). Browsers check for the same URL origin, so you can't use anything other than &lt;code&gt;mydomain.com/[something]&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;If we are talking about AWS and Serverless, the most affordable solution is to use AWS CloudFront.&lt;/p&gt;

&lt;p&gt;We can create two behaviors and redirect traffic to the correct Origin depending on the path pattern:&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;For this to work, CloudFront must have the correct certificates configured to accept requests using your DNS name.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This solution will solve the original problem, there are no more preflight requests or some kind of hacks.&lt;/p&gt;

&lt;p&gt;The edge location of the servers would be an additional bonus. Which will reduce the connection time of clients with the endpoint.&lt;/p&gt;

&lt;p&gt;The additional configuration of &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/origin-shield.html" rel="noopener noreferrer"&gt;CloudFront Origin Shield&lt;/a&gt; will allow you to speed up traffic to AppSync or other service by using an internal faster network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveat №1
&lt;/h2&gt;

&lt;p&gt;We have implemented this architecture:&lt;/p&gt;

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

&lt;p&gt;Since WAF works based on the IP address of the caller, it will not be the client's address but CloudFront's. Therefore, the request limit can be hit much faster and affect all users in the same location. &lt;/p&gt;

&lt;p&gt;To solve this, we can use the &lt;code&gt;X-Forwarded-For&lt;/code&gt; header to receive client IPs from CloudFront:&lt;/p&gt;

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

&lt;p&gt;But this will make our AppSync API insecure, and if someone finds out the unique address of our endpoint, they can attack through it.&lt;/p&gt;

&lt;p&gt;Therefore, it is more reliable to make two firewalls. Migrate the old one to handle requests in front of CloudFront. And make a new one between CloudFront and AppSync that will prohibit all requests not from CloudFront (make it directly inaccessible):&lt;/p&gt;

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

&lt;p&gt;To set up New WAF, you can deny all requests by default and allow only those that contain some secret header with a value that is configured on CloudFront. WAF &lt;a href="https://docs.amazonaws.cn/en_us/AWSCloudFormation/latest/UserGuide/aws-resource-wafv2-webacl.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for details.&lt;/p&gt;

&lt;p&gt;CloudFormation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;Rules&lt;/span&gt;&lt;span class="pi"&gt;:&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;Secret&lt;/span&gt;
      &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
      &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;byteMatchStatement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;searchString&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;fieldToMatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;singleHeader&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;Secret&lt;/span&gt;
          &lt;span class="na"&gt;textTransformations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&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;NONE&lt;/span&gt;
          &lt;span class="na"&gt;positionalConstraint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;EXACTLY'&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Caveat №2
&lt;/h2&gt;

&lt;p&gt;You should use the default сache policy (see below), even if you don't cache anything (POST requests are never cached). Because if you disable it, request compression will not work. &lt;/p&gt;

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

&lt;h2&gt;
  
  
  Caveat №3
&lt;/h2&gt;

&lt;p&gt;If you have more than one endpoint, you will not be able to set up a redirect because you cannot have two identical path patterns: '/graphql'.&lt;/p&gt;

&lt;p&gt;To resolve this, you can use some unique paths and setup &lt;a href="https://aws.amazon.com/en/blogs/aws/introducing-cloudfront-functions-run-your-code-at-the-edge-with-low-latency-at-any-scale/" rel="noopener noreferrer"&gt;CloudFront functions&lt;/a&gt; to redirect requests to the correct path for the Origin.&lt;/p&gt;

&lt;p&gt;CloudFormation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;CloudFrontGraphQLFunction&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::CloudFront::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${self:provider.stackName}-graphql-redirect&lt;/span&gt;
      &lt;span class="na"&gt;AutoPublish&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;FunctionCode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;function handler(event) {&lt;/span&gt;
          &lt;span class="s"&gt;var request = event.request;&lt;/span&gt;
          &lt;span class="s"&gt;request.uri = "/graphql"&lt;/span&gt;
          &lt;span class="s"&gt;return request;&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;FunctionConfig&lt;/span&gt;&lt;span class="pi"&gt;:&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;cloudfront-js-1.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Caveat №4
&lt;/h2&gt;

&lt;p&gt;When developing locally, you may have problems with the frontend and CORS. &lt;/p&gt;

&lt;p&gt;This is because the first response of the preflight request from localhost to the API does not contain all the required headers, even if you set the appropriate policy.&lt;/p&gt;

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

&lt;p&gt;CloudFront will add &lt;code&gt;access-control-allow-origin&lt;/code&gt;, but Chrome also needs (-headers and -methods). &lt;/p&gt;

&lt;p&gt;To avoid the need to use additional extensions like &lt;a href="https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf" rel="noopener noreferrer"&gt;this&lt;/a&gt;. You can configure additional headers using the CloudFront function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;CloudFrontGraphQLCorsFunction&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::CloudFront::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${self:provider.stackName}-graphql-cors&lt;/span&gt;
      &lt;span class="na"&gt;AutoPublish&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;FunctionCode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;function handler(event) {&lt;/span&gt;
          &lt;span class="s"&gt;var response  = event.response;&lt;/span&gt;
          &lt;span class="s"&gt;var headers  = response.headers;&lt;/span&gt;
          &lt;span class="s"&gt;headers['access-control-allow-origin'] = {value: "*"};&lt;/span&gt;
          &lt;span class="s"&gt;headers['access-control-allow-headers'] = {value: "*"};&lt;/span&gt;
          &lt;span class="s"&gt;headers['access-control-allow-methods'] = {value: "*"};&lt;/span&gt;
          &lt;span class="s"&gt;return response;&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;FunctionConfig&lt;/span&gt;&lt;span class="pi"&gt;:&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;cloudfront-js-1.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This approach should work not only with AppSync but also with other services, such as API Gateway.&lt;/p&gt;

&lt;p&gt;If you have any questions, please write, and I will gladly try to answer. &lt;/p&gt;

&lt;p&gt;Thanks&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>appsync</category>
      <category>graphql</category>
    </item>
  </channel>
</rss>
