<?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: Rich</title>
    <description>The latest articles on DEV Community by Rich (@richdevelops).</description>
    <link>https://dev.to/richdevelops</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%2F97277%2F6496bf4b-5f82-40d9-8d38-d757ffd68689.jpg</url>
      <title>DEV Community: Rich</title>
      <link>https://dev.to/richdevelops</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/richdevelops"/>
    <language>en</language>
    <item>
      <title>How to deploy Remix apps with SSR to AWS Amplify</title>
      <dc:creator>Rich</dc:creator>
      <pubDate>Tue, 02 Apr 2024 15:00:21 +0000</pubDate>
      <link>https://dev.to/aws-builders/how-to-deploy-remix-apps-with-ssr-to-aws-amplify-60m</link>
      <guid>https://dev.to/aws-builders/how-to-deploy-remix-apps-with-ssr-to-aws-amplify-60m</guid>
      <description>&lt;p&gt;The AWS Amplify team recently announced the Amplify Hosting deployment specification. This is a way to deploy applications to AWS Amplify with server side rendering (SSR) support. While there are guides or support for many frameworks including Astro, NextJS and Nuxt I couldn't find one for Remix.&lt;/p&gt;

&lt;p&gt;As I'm about to start working on a Shopify app (Shopify provides templates for Remix) and I didn't want to switch infrastructure providers I decided to investigate using how difficult it would be to use Amplify to host a Remix app with SSR.&lt;/p&gt;

&lt;p&gt;The first step is to create a build file named &lt;code&gt;amplify.yml&lt;/code&gt; which Amplify uses to build the project. I was deploying a new project and Amplify detected the file automatically during the initial deployment. Our build script handles four important tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Runs &lt;code&gt;npm run build&lt;/code&gt; to build the application. Remix puts the output in into the &lt;code&gt;build&lt;/code&gt; folder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Moves and restuctures the &lt;code&gt;build&lt;/code&gt; folder output to meet the Amplify hosting specification by&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reduce the size of our &lt;code&gt;node-modules&lt;/code&gt; folder by running &lt;code&gt;npm ci --omit dev&lt;/code&gt; to create a &lt;code&gt;node-modules&lt;/code&gt; that only includes production dependencies. This is important for larger project as there is a limit on the maximum size of this folder. It also helps reduce cold start times. The folder is moved into the &lt;code&gt;compute/default&lt;/code&gt; folder so the modules are available at runtime.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally there are two files that we will create shortly that need to be copied into place.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The complete &lt;code&gt;amplify.yml&lt;/code&gt; is below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: 1baseDirectory: .amplify-hostingfrontend: phases: preBuild: commands: - npm ci build: commands: - npm run build - mv build .amplify-hosting - mv .amplify-hosting/client .amplify-hosting/static - mkdir -p .amplify-hosting/compute - mv .amplify-hosting/server .amplify-hosting/compute/default - npm ci --omit dev - cp package.json .amplify-hosting/compute/default - cp -r node_modules .amplify-hosting/compute/default - cp server.js .amplify-hosting/compute/default - cp deploy-manifest.json .amplify-hosting/deploy-manifest.json artifacts: files: - "**/*" baseDirectory: .amplify-hosting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AWS Amplify will now package and deploy our &lt;code&gt;.amplify-hosting&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;deploy-mainfest.json&lt;/code&gt; has two main tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Tell Amplify how to route traffic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tell Amplify how to configure and start the compute resources&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This routing should not be confused with rewriting and redirecting traffic. It's purpose is to indicate if traffic should be handled as static or compute. Here I'm using the less than perfect approach that files with a &lt;code&gt;.&lt;/code&gt; should be treated as static first and fall back to compute if they don't exist. Everything else should be treated as compute. This means that static files must have a &lt;code&gt;.&lt;/code&gt; in the filename.&lt;/p&gt;

&lt;p&gt;For the compute configuration I've set the runtime to Node 20 and the project should be started by using &lt;code&gt;node server.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The full &lt;code&gt;deploly-mainfest.json&lt;/code&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ "version": 1, "framework": { "name": "remix", "version": "2.8.1" }, "routes": [{ "path": "/*.*", "target": { "kind": "Static", "cacheControl": "public, max-age=2" }, "fallback": { "kind": "Compute", "src": "default" } }, { "path": "/*", "target": { "kind": "Compute", "src": "default" } }], "computeResources": [{ "name": "default", "runtime": "nodejs20.x", "entrypoint": "server.js" }]}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally I need to create a small Javascript file called &lt;code&gt;server.js&lt;/code&gt;. This file launches an Express server on port 3000 which listens for requests and passes them to Remix. Remember to add express as a production dependency so this will work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i express --save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete server.js is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import remix from "@remix-run/express";import express from "express";import * as build from "./index.js";const app = express();const port = 3000;app.all( "*", remix.createRequestHandler({ build, }));app.listen(port, () =&amp;gt; { console.log(`Example app listening on port ${port}`)})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! You should be able to deploy the project to AWS Amplify and when you go to the URL provided your request will be execute on the server.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>awsamplify</category>
      <category>amplify</category>
      <category>remix</category>
    </item>
    <item>
      <title>Moving "server-only" to the top of the imports</title>
      <dc:creator>Rich</dc:creator>
      <pubDate>Mon, 01 Apr 2024 15:00:45 +0000</pubDate>
      <link>https://dev.to/richdevelops/moving-server-only-to-the-top-of-the-imports-1964</link>
      <guid>https://dev.to/richdevelops/moving-server-only-to-the-top-of-the-imports-1964</guid>
      <description>&lt;p&gt;Like many developers I use Prettier to keep my code base looking consistent. One of the plugins we use is @trivago/prettier-plugin-sort-imports which automatically sorts the order of our imports.&lt;/p&gt;

&lt;p&gt;While converting an old Gatsby website to NextJS we wanted to use the server-only package to prevent leaking secrets. By positioning it at the top of the file it would also allow us to quickly identify server only code. Of course the sort imports plugin had very different ideas and diligently moved it further down the list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { type ClientPerspective, type QueryParams, createClient } from "next-sanity";import "server-only";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fixing this should just require adding &lt;code&gt;^server-only$&lt;/code&gt; to the start of the &lt;code&gt;importOrder&lt;/code&gt; setting but the plugin keeping putting it last.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ "plugins": ["@trivago/prettier-plugin-sort-imports"], "importOrder": ["^server-only$", "^[./]"], "importOrderSeparation": true, "importOrderSortSpecifiers": true}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a little mucking around I realised that it was sorting the modules correctly and putting server-only first but the sorted import list always appeared after the third party modules. This made it look like the plugin wasn't working. Luckily fixing this was trivial thanks to the &lt;code&gt;&amp;lt;THIRD_PARTY_MODULES&amp;gt;&lt;/code&gt; placeholder that could be used to sort where they appeared.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ "plugins": ["@trivago/prettier-plugin-sort-imports"], "importOrder": ["^server-only$", "&amp;lt;THIRD_PARTY_MODULES&amp;gt;", "^[./]"], "importOrderSeparation": true, "importOrderSortSpecifiers": true}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With done that all my server-only imports moved to the top of their files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import "server-only";import { type ClientPerspective, type QueryParams, createClient } from "next-sanity";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>typescript</category>
      <category>react</category>
      <category>prettier</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Invalid Grant error with AWS IAM Identity Center</title>
      <dc:creator>Rich</dc:creator>
      <pubDate>Fri, 29 Mar 2024 09:00:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/invalid-grant-error-with-aws-iam-identity-center-35fl</link>
      <guid>https://dev.to/aws-builders/invalid-grant-error-with-aws-iam-identity-center-35fl</guid>
      <description>&lt;p&gt;Recently I was setting up a new computer which involved configuring the AWS CLI to use IAM Identity Center (formerly AWS SSO) to access my accounts. Normally this is a prety straight forward proposition. After running &lt;code&gt;aws configure sso&lt;/code&gt; command you need to provide four pieces of information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Session Name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Start URL&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Region&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Registration Scopes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS then authenticates you, you select your account, answer some more questions and it's done.&lt;/p&gt;

&lt;p&gt;This time I keep getting an invalid_grant error after I authenticated myself.&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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1710637519143%2Ff7815761-d066-469c-8aa1-911c29bc4d7a.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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1710637519143%2Ff7815761-d066-469c-8aa1-911c29bc4d7a.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem and solution turned out to be really simple. I selected the wrong region for IAM Identity Center. In my defence I mostly work with IAM Identity Center in my closest region but this was an older account and it was setup in a different region. Once I had the correct region everything worked correctly.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>awscloud</category>
      <category>awsiam</category>
      <category>awssso</category>
    </item>
    <item>
      <title>Solve the AppSync Lambda Resolver N+1 Problem with BatchInvoke</title>
      <dc:creator>Rich</dc:creator>
      <pubDate>Tue, 02 Jan 2024 13:00:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/solve-the-appsync-lambda-resolver-n1-problem-with-batchinvoke-3al2</link>
      <guid>https://dev.to/aws-builders/solve-the-appsync-lambda-resolver-n1-problem-with-batchinvoke-3al2</guid>
      <description>&lt;p&gt;If you're not familiar with the GraphQL N+1 problem then consider the following query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query { authors { id name books { id name } }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "authors" resolver is executed once and returns a list of N authors. The "books" resolver is then executed once for each author. This is the N+1 problem because you execute N resolvers for the books plus 1 for the authors.&lt;/p&gt;

&lt;p&gt;As your query becomes deeper the number of resolvers executed increases significantly. Let's assume there are 10 authors, each with 5 books. To fetch the authors and books there are 11 resolvers executed but if we also request publisher information for each book we now execute 61 resolvers (1 + 10 + 10 x 5).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query { authors { id name books { id name publisher { id name } } }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When configuring AppSync to use a Lambda function as a resolver you can use the &lt;code&gt;Invoke&lt;/code&gt; or &lt;code&gt;BatchInvoke&lt;/code&gt; operation.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Invoke&lt;/code&gt; operation will cause AppSync to invoke your Lambda function every time it needs to execute the resolver. For the "authors" resolver in our example that is acceptable because it is only executed once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { AppSyncResolverHandler } from 'aws-lambda';export const authorsHandler: AppSyncResolverHandler&amp;lt;unknown, { id: string; name: string }[]&amp;gt; = async (event) =&amp;gt; { const authors = [{ id: '1', name: 'Tajeddigt Olufemi' }, { id: '2', name: 'Lelio Miodrag' }, { id: '3', name: 'Aineias Vladimir' }, { id: '4', name: 'Sachin Lamya' }, { id: '5', name: 'Kamakshi Cosme' }, { id: '6', name: 'James Sharmila' }, { id: '7', name: 'Holden Wulffld' }, { id: '8', name: 'Quidel Bahdan' }, { id: '9', name: 'Reinout Johanna' }, { id: '10', name: 'Til Nikica' }, { id: '11', name: 'Metodj Maxima' }, { id: '12', name: 'nh Ester' }, { id: '13', name: 'Mxim Kristina' }, { id: '14', name: 'Yedidia Jafar' }, { id: '15', name: 'Lone Mariusz' }, { id: '16', name: 'Vitya Franjo' }, { id: '17', name: 'Malvolio Lochlann' }, { id: '18', name: 'Evette Dierk' }, { id: '19', name: 'Nnenna Basileios' }, { id: '20', name: 'Dmitrei Iya' },]; return authors;};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the "books" resolver we really want to use the &lt;code&gt;BatchInvoke&lt;/code&gt; operation. To use it with the direct Lambda resolver integration add the &lt;code&gt;MaxBatchSize&lt;/code&gt; property to your resolver definition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  BooksResolver: Type: AWS::AppSync::Resolver Properties: ApiId: !GetAtt Api.ApiId DataSourceName: !GetAtt BooksDataSource.Name TypeName: Author FieldName: books MaxBatchSize: 1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AppSync will now invoke your Lambda with an array of up to &lt;code&gt;MatchBatchSize&lt;/code&gt; events. This allows your Lambda to process multiple resolvers at once instead of individually.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { AppSyncBatchResolverHandler } from 'aws-lambda';export const lambdaHandler: AppSyncBatchResolverHandler&amp;lt; unknown, { id: string; name: string }[], { id: string; name: string }&amp;gt; = async (events) =&amp;gt; { return events.map((event) =&amp;gt; { const books: { id: string; name: string }[] = []; for (let i = 1; i &amp;lt;= 20; i++) { books.push({ id: `${event.source.id}-${i}`, name: `Book ${i}` }); } return books; });};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By batch handling resolver executions in a single Lambda invocation you can improve performance by reducing the number of cold starts and lower costs through invoking less Lambda functions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tip: In production you may want to experiment with values for&lt;/em&gt;&lt;code&gt;MaxBatchSize&lt;/code&gt;&lt;em&gt;as you are trading off the risk of a cold start and cost versus being able to process requests in parallel.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What about our publishers?&lt;/p&gt;

&lt;p&gt;You should always use &lt;code&gt;BatchInvoke&lt;/code&gt; for nested Lambda resolvers when possible. At each level of nesting AppSync will determine the events that need to be sent to your Lambda resolver then pass them in array of &lt;code&gt;MaxBatchSize&lt;/code&gt; length resulting in the minimum number of Lambda functions being executed. With a sufficiently high enough &lt;code&gt;MaxBatchSize&lt;/code&gt; value you may only need to invoke one Lambda function for each level of nesting in your query.&lt;/p&gt;

&lt;p&gt;By using &lt;code&gt;BatchInvoke&lt;/code&gt; you may also be able to optimize your resolvers through more efficient processing of requests.&lt;/p&gt;

&lt;p&gt;In our example we are requesting information about the publisher for each book. It's likely that publishers will be repeated many times. If we used &lt;code&gt;Invoke&lt;/code&gt; then each resolver execution would use &lt;code&gt;GetItem&lt;/code&gt; to fetch the published record from DynamoDB. This means the same publisher record would be retrieved multiple times. With &lt;code&gt;BatchInvoke&lt;/code&gt; we can filter a list of unique publisher ID's, use &lt;code&gt;BatchGetItem&lt;/code&gt; to fetch them all at once from DynamoDB then map over the events to return the correct publish record for each resolver. This reduces our usage of DynamoDB and makes it quicker.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>appsync</category>
      <category>lambda</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Sprint Timeline</title>
      <dc:creator>Rich</dc:creator>
      <pubDate>Wed, 17 Mar 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/richdevelops/sprint-timeline-45np</link>
      <guid>https://dev.to/richdevelops/sprint-timeline-45np</guid>
      <description>&lt;p&gt;If you need to run an agile sprint timeline this document might help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timeline
&lt;/h2&gt;

&lt;p&gt;The timeline for a 2 week sprint&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Day of Week&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;-5&lt;/td&gt;
&lt;td&gt;Wednesday&lt;/td&gt;
&lt;td&gt;Sprint Planning Meeting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-4&lt;/td&gt;
&lt;td&gt;Thursday&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-3&lt;/td&gt;
&lt;td&gt;Friday&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-2&lt;/td&gt;
&lt;td&gt;Saturday&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-1&lt;/td&gt;
&lt;td&gt;Sunday&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Monday&lt;/td&gt;
&lt;td&gt;Sprint Grooming / Sprint Start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Tuesday&lt;/td&gt;
&lt;td&gt;Standup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Wednesday&lt;/td&gt;
&lt;td&gt;Standup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Thursday&lt;/td&gt;
&lt;td&gt;Standup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Friday&lt;/td&gt;
&lt;td&gt;Standup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Saturday&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Sunday&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Monday&lt;/td&gt;
&lt;td&gt;Standup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Tuesday&lt;/td&gt;
&lt;td&gt;Standup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Wednesday&lt;/td&gt;
&lt;td&gt;Standup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Thursday&lt;/td&gt;
&lt;td&gt;Standup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Friday&lt;/td&gt;
&lt;td&gt;Standup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Monday&lt;/td&gt;
&lt;td&gt;Final Day / Standup / Retro&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Points
&lt;/h2&gt;

&lt;p&gt;The number of points a developer should target during the sprint depends on the number of days they are working.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Days Worked&lt;/th&gt;
&lt;th&gt;Min Points&lt;/th&gt;
&lt;th&gt;Max Points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When allocating points to a task use the following table. If a story takes longer than 5 days it should be broken down into multiple stories.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Points&lt;/th&gt;
&lt;th&gt;Days&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;½&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1-2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2-3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>agile</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Install nvm and Node.js for macOS 11</title>
      <dc:creator>Rich</dc:creator>
      <pubDate>Mon, 15 Mar 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/richdevelops/install-nvm-and-node-js-for-macos-11-2phj</link>
      <guid>https://dev.to/richdevelops/install-nvm-and-node-js-for-macos-11-2phj</guid>
      <description>&lt;p&gt;The &lt;a href="https://github.com/nvm-sh/nvm"&gt;Node Version Manager&lt;/a&gt; is an easy to way to run multiple versions of Node. Having upgraded to macOS 11 recently I found that I had accidentally installed a Node using Homebrew and I needed to get nvm working again.&lt;/p&gt;

&lt;p&gt;The process was fairly simple.&lt;/p&gt;

&lt;p&gt;Uninstall the Homebrew Node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew uninstall node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try to install the Homebrew nvm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;nvm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, this didn't work so well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;######################################################################## 100.0%
Error: Your Command Line Tools (CLT) does not support macOS 11.
It is either outdated or was modified.
Please update your Command Line Tools (CLT) or delete it if no updates are available.
Update them from Software Update in System Preferences or run:
  softwareupdate --all --install --force

If that doesn't show you any updates, run:
  sudo rm -rf /Library/Developer/CommandLineTools
  sudo xcode-select --install

Alternatively, manually download them from:
  https://developer.apple.com/download/more/.

Error: An exception occurred within a child process:
  SystemExit: exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I knew I was running the latest version of everything so I jumped straight to:&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;sudo rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /Library/Developer/CommandLineTools
&lt;span class="nb"&gt;sudo &lt;/span&gt;xcode-select &lt;span class="nt"&gt;--install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that done I could finally install nvm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;nvm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then cleaned out my old &lt;code&gt;.nvm&lt;/code&gt; folder and created a new one:&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;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; ~/.nvm &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/.nvm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I switched from bash to zsh when upgrading to macOS 11 I had to add some lines to my &lt;code&gt;~/.zshrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NVM_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.nvm"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"/usr/local/opt/nvm/nvm.sh"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"/usr/local/opt/nvm/nvm.sh"&lt;/span&gt; &lt;span class="c"&gt;# This loads nvm&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"/usr/local/opt/nvm/etc/bash_completion.d/nvm"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"/usr/local/opt/nvm/etc/bash_completion.d/nvm"&lt;/span&gt; &lt;span class="c"&gt;# This loads nvm bash_completion&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before continuing I ran the &lt;code&gt;source&lt;/code&gt; command but you could simply restart your shell:&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;source&lt;/span&gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was now time to install the version of Node I required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvm &lt;span class="nb"&gt;install &lt;/span&gt;v14.16.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, check that Node is installed correctly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>node</category>
      <category>npm</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>AppSync Resolver Optimization by Removing Unnecessary GetItem's</title>
      <dc:creator>Rich</dc:creator>
      <pubDate>Sun, 16 Aug 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/richdevelops/appsync-resolver-optimization-by-removing-unnecessary-getitem-s-d2i</link>
      <guid>https://dev.to/richdevelops/appsync-resolver-optimization-by-removing-unnecessary-getitem-s-d2i</guid>
      <description>&lt;p&gt;I had a front end developer comment that one of API's was slow. The developer was trying download all of the products and their related image. After a quick investigation it turned out we had hit the dreaded N+1 problem.&lt;/p&gt;

&lt;p&gt;Using AppSync's new context info object we've been able to apply a simple optimization that has significantly reduced the number of DynamoDB queries our API was making.&lt;/p&gt;

&lt;p&gt;A simplified version of the schema would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Image {
  id: ID!
  url: String
}

type Product {
  id: ID!
  name: String
  image: Image
}

type Query {
  listImages: [Image]
  listProducts: [Products]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AppSync resolvers for &lt;code&gt;Query.listProducts&lt;/code&gt; and &lt;code&gt;Query.listImages&lt;/code&gt; both performed a DynamoDB &lt;code&gt;Query&lt;/code&gt; allowing one request to return multiple records. The &lt;code&gt;Product.image&lt;/code&gt; resolver used a &lt;code&gt;GetItem&lt;/code&gt; to fetch the image for each product.&lt;/p&gt;

&lt;p&gt;In my head the developer would use a query like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query ListProductsWithImage {
  listProducts {
    id
    name
    image {
      id
      url
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might notice a problem here. While &lt;code&gt;listProducts&lt;/code&gt; can be performed with a single DynamoDB operation I'm performing another DynamoDB operation for every record that it returns. The N+1 problem.&lt;/p&gt;

&lt;p&gt;It turned out that the developer was actually using two queries and joining the data in the application. The first downloaded all of the images&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query ListImages {
  listImages {
    id
    url
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second all of the products&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query ListProducts {
  listProducts {
    id
    name
    image {
      id
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this could have been efficient it wasn't because the &lt;code&gt;Product.image&lt;/code&gt; resolver was still performing a &lt;code&gt;GetItem&lt;/code&gt; for each product returned by &lt;code&gt;Query.listProducts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This was crazy because we store the &lt;code&gt;Image&lt;/code&gt; &lt;code&gt;id&lt;/code&gt; in the &lt;code&gt;imageId&lt;/code&gt; attribute as part of the &lt;code&gt;Product&lt;/code&gt; record in DynamoDB.&lt;/p&gt;

&lt;p&gt;One solution would be to expose the &lt;code&gt;imageId&lt;/code&gt; in &lt;code&gt;Product&lt;/code&gt; but this is discouraged in GraphQL schema design because it exposes the internal implementation details to an external client making it harder to change the implementation later.&lt;/p&gt;

&lt;p&gt;With the new &lt;code&gt;$ctx.info&lt;/code&gt; object and early returning I can now optimize my resolvers for this use case by adding the following code at the top of the &lt;code&gt;Product.image&lt;/code&gt; request resolver.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#if ($ctx.info.selectionSetList.size() == 1 &amp;amp;&amp;amp; $ctx.info.selectionSetList[0] == "id")
  #return({ "id": "$ctx.source.imageId" })
#end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;id&lt;/code&gt; is the only &lt;code&gt;image&lt;/code&gt; field requested it will now return early, before the &lt;code&gt;GetItem&lt;/code&gt; operation, using &lt;code&gt;$ctx.source.imageId&lt;/code&gt; as the &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This small optimization improves API performance and saves money by removing unnecessary &lt;code&gt;GetItem&lt;/code&gt; requests without exposing the internal implementation but still allows the client to fetch additional fields if required.&lt;/p&gt;

</description>
      <category>appsync</category>
      <category>aws</category>
      <category>dynamodb</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Disabling Cognito User Pools Authentication for a Single Mutation with AppSync</title>
      <dc:creator>Rich</dc:creator>
      <pubDate>Thu, 28 May 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/disabling-cognito-user-pools-authentication-for-a-single-mutation-with-appsync-kdp</link>
      <guid>https://dev.to/aws-builders/disabling-cognito-user-pools-authentication-for-a-single-mutation-with-appsync-kdp</guid>
      <description>&lt;p&gt;Typically I combined AppSync with Cognito User Pools for authorization. This works great for API's where the user is logged in but what if you need a few queries or mutations to work for users who aren't logged in?&lt;/p&gt;

&lt;p&gt;A common example of this is a mutation that allows people to register.&lt;/p&gt;

&lt;p&gt;While AppSync doesn't allow unauthenticated requests you can use API key authorization to get around the need for a user to be logged in.&lt;/p&gt;

&lt;p&gt;Start by setting up AppSync with Cognito User Pools as the default authorization mode and API key as an additional authorization provider. This will protect the API using Cognito User Pools authorization but allow us to enable API key authorization for some fields.&lt;/p&gt;

&lt;p&gt;Next I need a schema with a mutation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Account&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="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AWSEmail&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&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="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RegisterAccountInput&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="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AWSEmail&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c"&gt;## Other registration fields&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="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Query&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="n"&gt;findAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Account&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="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mutation&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="n"&gt;registerAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RegisterAccountInput&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Account&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;By default the entire schema can only be accessed if the request uses Cognito User Pools authorization. By adding &lt;code&gt;@aws_api_key @aws_cognito_user_pools&lt;/code&gt; to a type/field you can allow both authorization methods. If you only want to allow API key then you can use &lt;code&gt;@aws_api_key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the following example I've limited access to the &lt;code&gt;registerAccount&lt;/code&gt; mutation to only requests using API key authorization and you can only access the &lt;code&gt;id&lt;/code&gt; field on the &lt;code&gt;Account&lt;/code&gt; that it returns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Account&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="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;aws_api_key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;aws_cognito_user_pools&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AWSEmail&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&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="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RegisterAccountInput&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="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AWSEmail&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c"&gt;## Other registration fields&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="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Query&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="n"&gt;findAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Account&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="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mutation&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="n"&gt;registerAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RegisterAccountInput&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;aws_api_key&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;For the user to access the other &lt;code&gt;Account&lt;/code&gt; fields they would need to switch to Cognito User Pools authorization and use &lt;code&gt;findAccount&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>apsync</category>
      <category>aws</category>
      <category>serverless</category>
      <category>cognito</category>
    </item>
    <item>
      <title>How to build an AppSync API using a single table DynamoDB design</title>
      <dc:creator>Rich</dc:creator>
      <pubDate>Fri, 15 May 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/richdevelops/how-to-build-an-appsync-api-using-a-single-table-dynamodb-design-ibe</link>
      <guid>https://dev.to/richdevelops/how-to-build-an-appsync-api-using-a-single-table-dynamodb-design-ibe</guid>
      <description>&lt;p&gt;If you want to start a flame war in the AWS AppSync community then ask if you should use a single or multiple table design for DynamoDB. There's no shortage of opinions in both directions 🔥🔥🔥&lt;/p&gt;

&lt;p&gt;In this article I'm going to show you how I built a generic single table DynamoDB solution for AWS AppSync.&lt;/p&gt;

&lt;h2&gt;
  
  
  Historical Context
&lt;/h2&gt;

&lt;p&gt;Before I explain "how" it's important to remember that DynamoDB did not support on-demand capacity (pay per use pricing) when AppSync was originally released. Managing the read and write capacity across multiple tables was a lot of extra work. Under provisioning could result in your API failing and over provisioning was expensive.&lt;/p&gt;

&lt;p&gt;By using a single table I could reduce the amount of time spent managing table capacity and save a lot of money. That is why began exploring how to build an AppSync API using only a single DynamoDB table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Primary Key
&lt;/h2&gt;

&lt;p&gt;For the tables primary key I used a generic attribute named &lt;code&gt;PK&lt;/code&gt; that &lt;em&gt;does not&lt;/em&gt; appear in the GraphQL schema. Keeping the primary key for the table out of the GraphQL schema allows me to use any field in the GraphQL type as the primary ID for a record.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: To keep my examples simple I will use &lt;code&gt;id&lt;/code&gt; as the primary field but you can use a different field or fields.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Mapping the primary ID for the GraphQL type to &lt;code&gt;PK&lt;/code&gt; is handled in the AppSync resolver request template. There are two patterns I recommend when mapping fields to &lt;code&gt;PK&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;NODE#${id}&lt;/code&gt; if you have globally unique ID's&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;${__typename}#${id}&lt;/code&gt; if you do not have globally unique ID's&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example: Let's start with a simple GraphQL schema for farms.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Farm {
  id: ID!
  name: String
}

input CreateFarmInput {
  name: String!
}

type Mutation {
  createFarm(input: CreateFarmInput!): Farm
}

type Query {
  findFarm(id: ID!): Farm
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request resolver template for &lt;code&gt;createFarm&lt;/code&gt; would be&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#set( $id = $util.autoId() )
$util.qr($ctx.args.input.put("id", $id))
$util.qr($ctx.args.input.put("__typename", "Farm"))

{
  "version" : "2017-02-28",
  "operation" : "PutItem",
  "key" : {
    "PK": $util.dynamodb.toDynamoDBJson("NODE#$id"),
  },
  "attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This template generates an &lt;code&gt;id&lt;/code&gt; for the new farm that is added to the input along with the &lt;code&gt;__typename&lt;/code&gt;. It then prefixes that ID with &lt;code&gt;NODE#&lt;/code&gt; to generate the &lt;code&gt;PK&lt;/code&gt; value for the DynamoDB key.&lt;/p&gt;

&lt;p&gt;When invoked with the following input&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mutation {
  createFarm(input: { name: "Old MacDonald's" }) {
    id
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will create a record in DynamoDB similar to&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PK&lt;/th&gt;
&lt;th&gt;__typename&lt;/th&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NODE#1234&lt;/td&gt;
&lt;td&gt;Farm&lt;/td&gt;
&lt;td&gt;1234&lt;/td&gt;
&lt;td&gt;Old MacDonald's&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The farm can then be retrieved using &lt;code&gt;findFarm&lt;/code&gt; that accepts an &lt;code&gt;id&lt;/code&gt; which is mapped to the &lt;code&gt;PK&lt;/code&gt; attribute in the request template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "version": "2017-02-28",
  "operation": "GetItem",
  "key": {
    "PK": $util.dynamodb.toDynamoDBJson("NODE#$ctx.args.id"),
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  SECURITY WARNING
&lt;/h4&gt;

&lt;p&gt;When using the &lt;code&gt;NODE#${id}&lt;/code&gt; pattern you &lt;strong&gt;must&lt;/strong&gt; check the &lt;code&gt;__typename&lt;/code&gt; is one that you expect in &lt;strong&gt;every&lt;/strong&gt; resolver that fetches, updates or deletes records. Failure to do this may allow someone to by pass security by using the ID from one type with the resolver for a different type that is more permissive.&lt;/p&gt;

&lt;p&gt;The examples in this article &lt;strong&gt;do not&lt;/strong&gt; include this check because I am focusing on the DynamoDB access pattern and not AppSync access controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unique Secondary Fields
&lt;/h2&gt;

&lt;p&gt;Requiring additional fields in our GraphQL type to be unique is a common problem. The &lt;code&gt;Farm&lt;/code&gt; type contains an &lt;code&gt;id&lt;/code&gt; field that uniquely identifies a record and never changes. It also has a &lt;code&gt;name&lt;/code&gt; field that can change but must be unique.&lt;/p&gt;

&lt;p&gt;With DynamoDB only the primary key on the table is unique. We can't add this constraint to other attributes. To solve this I can use &lt;code&gt;TransactionWriteItems&lt;/code&gt; to write a record under different keys. The first key is the &lt;code&gt;NODE#${id}&lt;/code&gt; as previously discussed. The second key uses the pattern &lt;code&gt;${__typename}#${fieldname}#${value}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;createFarm&lt;/code&gt; resolver request template will now look like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#set( $id = $util.autoId() )
$util.qr($ctx.args.input.put("id", $id))
$util.qr($ctx.args.input.put("__typename", "Farm"))

{
  "version": "2018-05-29",
  "operation": "TransactWriteItems",
  "transactItems": [
    {
      "table": "YOUR-TABLE-NAME",
      "operation": "PutItem",
      "key": {
        "PK": $util.dynamodb.toDynamoDBJson("NODE#$id")
      },
      "attributeValues": $util.dynamodb.toMapValuesJson($ctx.args.input),
      "condition": {
        "expression": "attribute_not_exists(PK)",
        "returnValuesOnConditionCheckFailure": false
      }
    },
    {
      "table": "YOUR-TABLE-NAME",
      "operation": "PutItem",
      "key": {
        "PK": $util.dynamodb.toDynamoDBJson("Farm#name#$ctx.args.input.name.toLowerCase()")
      },
      "attributeValues": $util.dynamodb.toMapValuesJson($ctx.args.input),
      "condition": {
        "expression": "attribute_not_exists(PK)",
        "returnValuesOnConditionCheckFailure": false
      }
    },
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because I need to use the &lt;code&gt;2018-05-29&lt;/code&gt; resolver template version for &lt;code&gt;TransactionWriteItems&lt;/code&gt; and I only want to return one record my resolver response template is a little more complicated&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#if ( $ctx.error )
  $util.error($ctx.error.message, $ctx.error.type)
#end
$util.toJson($ctx.args.input)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main thing to understand is that I'm returning the &lt;code&gt;$ctx.args.input&lt;/code&gt; instead of &lt;code&gt;$ctx.result&lt;/code&gt;. This contains our &lt;code&gt;Farm&lt;/code&gt; type with the &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;__typename&lt;/code&gt; set correctly by the request template.&lt;/p&gt;

&lt;p&gt;In DynamoDB the records will be stored as:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PK&lt;/th&gt;
&lt;th&gt;__typename&lt;/th&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NODE#1234&lt;/td&gt;
&lt;td&gt;Farm&lt;/td&gt;
&lt;td&gt;1234&lt;/td&gt;
&lt;td&gt;Old MacDonald's&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Farm#name#old macdonald's&lt;/td&gt;
&lt;td&gt;Farm&lt;/td&gt;
&lt;td&gt;1234&lt;/td&gt;
&lt;td&gt;Old MacDonald's&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If I call the &lt;code&gt;createFarm&lt;/code&gt; mutation twice with the same name it will now fail because a record with the key &lt;code&gt;Farm#name#old macdonald's&lt;/code&gt; already exists.&lt;/p&gt;

&lt;p&gt;You may have noticed that I converted the name to lower case before using it in the &lt;code&gt;PK&lt;/code&gt; attribute for the second record. This prevents people from getting around the unique name restriction by using different combinations of upper and lower case letters. The &lt;code&gt;name&lt;/code&gt; attribute itself still retains the original case and that's the value the user will see when the record is queried via GraphQL.&lt;/p&gt;

&lt;p&gt;As a side benefit I now have a fast way to retrieve a &lt;code&gt;Farm&lt;/code&gt; record from the &lt;code&gt;name&lt;/code&gt; using &lt;code&gt;GetItem&lt;/code&gt;. This is useful if you have a natural key like email address or ISBN that you want to fetch records by.&lt;/p&gt;

&lt;h2&gt;
  
  
  One to One Relationships
&lt;/h2&gt;

&lt;p&gt;One to One relationships are simple to implement. Let's add a &lt;code&gt;logo&lt;/code&gt; to the &lt;code&gt;Farm&lt;/code&gt; that returns an &lt;code&gt;Image&lt;/code&gt; type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Image {
  id: ID!
  url: String
}

type Farm {
  id: ID!
  name: String
  logo: Image
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will also add a &lt;code&gt;logoId&lt;/code&gt; field to the input for &lt;code&gt;createFarm&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mutation {
  createFarm(input: { name: "Old MacDonald's", logoId: "1111" }) {
    id
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In DynamoDB this will be saved as&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PK&lt;/th&gt;
&lt;th&gt;__typename&lt;/th&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;imageId&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;url&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NODE#1234&lt;/td&gt;
&lt;td&gt;Farm&lt;/td&gt;
&lt;td&gt;1234&lt;/td&gt;
&lt;td&gt;1111&lt;/td&gt;
&lt;td&gt;Old MacDonald's&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NODE#1111&lt;/td&gt;
&lt;td&gt;Image&lt;/td&gt;
&lt;td&gt;1111&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;logo.gif&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When the request resolver template for &lt;code&gt;logo&lt;/code&gt; is executed the DynamoDB record for the farm is available in &lt;code&gt;$ctx.source&lt;/code&gt;. This allows us to perform a &lt;code&gt;GetItem&lt;/code&gt; with the correct &lt;code&gt;PK&lt;/code&gt; value to retrieve the &lt;code&gt;Image&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "version": "2017-02-28",
  "operation": "GetItem",
  "key": {
    "PK": $util.dynamodb.toDynamoDBJson("NODE#$ctx.source.logoId"),
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Traversing both directions in a one to one relationship requires storing the ID for the opposite side in each record. In our example this means storing the &lt;code&gt;imageId&lt;/code&gt; with the &lt;code&gt;Farm&lt;/code&gt; and &lt;code&gt;farmId&lt;/code&gt; with the &lt;code&gt;Image&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Many to One Relationships
&lt;/h2&gt;

&lt;p&gt;Many to One relationships work almost exactly the same as one to one relationships. On the many side you need to store the ID for the one side of the relationship.&lt;/p&gt;

&lt;p&gt;Let's expand our farm to include cow's.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Cow {
  id: ID!
  name: String
  farm: Farm
}

input CreateCowInput {
  name: String!
  farmId: ID!
}

type Farm {
  id: ID!
  name: String
}

input CreateFarmInput {
  name: String!
}

type Mutation {
  createCow(input: CreateCowInput!): Cow
  createFarm(input: CreateFarmInput!): Farm
}

type Query {
  findFarm(id: ID!): Farm
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create a new cow we would execute a mutation like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mutation {
  createCow(input: { name: "Bessie", farmId: "5678" }) {
    id
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request resolver template for &lt;code&gt;createCow&lt;/code&gt; is the same as &lt;code&gt;createFarm&lt;/code&gt; with the &lt;code&gt;__typename&lt;/code&gt; changed from &lt;code&gt;Farm&lt;/code&gt; to &lt;code&gt;Cow&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#set( $id = $util.autoId() )
$util.qr($ctx.args.input.put("id", $id))
$util.qr($ctx.args.input.put("__typename", "Cow"))

{
  "version" : "2017-02-28",
  "operation" : "PutItem",
  "key" : {
    "PK": $util.dynamodb.toDynamoDBJson("NODE#$id"),
  },
  "attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a DynamoDB record similar to:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PK&lt;/th&gt;
&lt;th&gt;__typename&lt;/th&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;farmId&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NODE#1234&lt;/td&gt;
&lt;td&gt;Cow&lt;/td&gt;
&lt;td&gt;1234&lt;/td&gt;
&lt;td&gt;5678&lt;/td&gt;
&lt;td&gt;Bessie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NODE#5678&lt;/td&gt;
&lt;td&gt;Farm&lt;/td&gt;
&lt;td&gt;5678&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Old MacDonald's&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Our request resolver for the &lt;code&gt;farm&lt;/code&gt; field on the &lt;code&gt;Cow&lt;/code&gt; type would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "version": "2017-02-28",
  "operation": "GetItem",
  "key": {
    "PK": $util.dynamodb.toDynamoDBJson("NODE#$ctx.source.farmId"),
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  One to Many Relationships
&lt;/h2&gt;

&lt;p&gt;One to Many relationships are more complicated and require writing additional attributes to the DynamoDB record. To implement these I use generic GSI's named &lt;code&gt;GSI01&lt;/code&gt;, &lt;code&gt;GSI02&lt;/code&gt;, &lt;code&gt;GSI03&lt;/code&gt;, etc. Each of these will have a corresponding partition key &lt;code&gt;PK01&lt;/code&gt;, &lt;code&gt;PK02&lt;/code&gt;, &lt;code&gt;PK03&lt;/code&gt;, etc and sort key &lt;code&gt;SK01&lt;/code&gt;, &lt;code&gt;SK02&lt;/code&gt;, &lt;code&gt;SK03&lt;/code&gt;. The additional data for these attributes is added by the resolver request template during the create/update mutation.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;PKxx&lt;/code&gt; you typically write the value &lt;code&gt;${ __typename}#${othersideId}&lt;/code&gt; where &lt;code&gt;__ typename&lt;/code&gt; is the name of the type you're writing data for (the many side) and &lt;code&gt;othersideId&lt;/code&gt; is the ID for the one side. The value for &lt;code&gt;SKxx&lt;/code&gt; will depend on how you want sort the results when traversing from the one to the many. I'll explain this further down. All of this should be done in the resolver template so these details are not exposed in the GraphQL schema.&lt;/p&gt;

&lt;p&gt;Let's make a tiny change to our farm schema adding a connection between &lt;code&gt;Farm&lt;/code&gt; and &lt;code&gt;Cow&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Farm {
  id: ID!
  name: String
  cows: [Cow]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything else will remain the same.&lt;/p&gt;

&lt;p&gt;When retrieving the cows from the farm I want them sorted by name. To do this I'll copy the &lt;code&gt;name&lt;/code&gt; value into &lt;code&gt;SK01&lt;/code&gt;. With the extra information our &lt;code&gt;createCow&lt;/code&gt; request template now looks like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#set( $id = $util.autoId() )
$util.qr($ctx.args.input.put("id", $id))
$util.qr($ctx.args.input.put("__typename", "Cow"))
$util.qr($ctx.args.input.put("PK01", "Cow#$ctx.args.input.farmId"))
$util.qr($ctx.args.input.put("SK01", $ctx.args.input.name))

{
    "version" : "2017-02-28",
    "operation" : "PutItem",
    "key" : {
        "PK": $util.dynamodb.toDynamoDBJson("NODE#$id"),
    },
    "attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When create cow is called with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mutation {
  createCow(input: { name: "Bessie", farmId: "5678" }) {
    id
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cow DyanmoDB record is now written as:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PK&lt;/th&gt;
&lt;th&gt;__typename&lt;/th&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;farmId&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;PK01&lt;/th&gt;
&lt;th&gt;SK01&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NODE#1234&lt;/td&gt;
&lt;td&gt;Cow&lt;/td&gt;
&lt;td&gt;1234&lt;/td&gt;
&lt;td&gt;5678&lt;/td&gt;
&lt;td&gt;Bessie&lt;/td&gt;
&lt;td&gt;Farm#5678&lt;/td&gt;
&lt;td&gt;Bessie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NODE#5678&lt;/td&gt;
&lt;td&gt;Farm&lt;/td&gt;
&lt;td&gt;5678&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Old MacDonald's&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To get from farm to cow we want the &lt;code&gt;cows&lt;/code&gt; resolver request template to &lt;code&gt;Query&lt;/code&gt; the &lt;code&gt;GS01&lt;/code&gt; index for a &lt;code&gt;PK01&lt;/code&gt; value that is the farms ID prefixed by the return type. Our &lt;code&gt;cows&lt;/code&gt; resolver request template is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "version" : "2017-02-28",
    "operation" : "Query",
    "index": "GS01",
    "query" : {
        "expression": "PK01 = :pk",
        "expressionValues" : {
            ":pk" : $util.dynamodb.toDynamoDBJson("Cow#$ctx.source.id")
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's continue expanding the farm by adding chickens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Chicken {
  id: ID!
  name: String
  farm: Farm
}

type Cow {
  id: ID!
  name: String
  farm: Farm
}

input CreateChickenInput {
  name: String!
  farmId: ID!
}

input CreateCowInput {
  name: String!
  farmId: ID!
}

type Farm {
  id: ID!
  name: String
  chickens: [Chicken]
  cows: [Cow]
}

type Mutation {
  createChicken(input: CreateChickenInput!): Chicken
  createCow(input: CreateCowInput!): Cow
}

type Query {
  farm(id: ID!): Farm
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can copy the resolver request templates from &lt;code&gt;createCow&lt;/code&gt; to &lt;code&gt;createChicken&lt;/code&gt; and &lt;code&gt;cows&lt;/code&gt; to &lt;code&gt;chickens&lt;/code&gt;. Just remember to replace &lt;code&gt;Cow&lt;/code&gt; with &lt;code&gt;Chicken&lt;/code&gt; inside the templates.&lt;/p&gt;

&lt;p&gt;What you might have noticed is that I did not need to create a new GSI. By prefixing the &lt;code&gt;farmId&lt;/code&gt; with the &lt;code&gt;__typename&lt;/code&gt; we are preventing a data collision which allows us to use the same GSI once for each GraphQL type. This slows the growth in GSI's because the number of GSI's for the table is determined by the GraphQL type using the largest number of GSI's.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;${returnTypename}#${context.source.id}&lt;/code&gt; pattern also works when a field returns multiple types. Let's modify the schema by adding &lt;code&gt;Animal&lt;/code&gt; which can be a &lt;code&gt;Chicken&lt;/code&gt; or &lt;code&gt;Cow&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;union Animal = Chicken | Cow

type Chicken {
  id: ID!
  name: String
  farm: Farm
}

type Cow {
  id: ID!
  name: String
  farm: Farm
}

input CreateChickenInput {
  name: String!
  farmId: ID!
}

input CreateCowInput {
  name: String!
  farmId: ID!
}

type Farm {
  id: ID!
  name: String
  chickens: [Chicken]
  cows: [Cow]
  animals: [Animal]
}

type Mutation {
  createChicken(input: CreateChickenInput!): Chicken
  createCow(input: CreateCowInput!): Cow
}

type Query {
  farm(id: ID!): Farm
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make this work I need a second GSI's. In &lt;code&gt;PK02&lt;/code&gt; I'll store &lt;code&gt;Animal#${farmId}&lt;/code&gt; using the &lt;code&gt;createChicken&lt;/code&gt; and &lt;code&gt;createCow&lt;/code&gt; resolver request templates. Here's what &lt;code&gt;createCow&lt;/code&gt; now looks like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#set( $id = $util.autoId() )
$util.qr($ctx.args.input.put("id", $id))
$util.qr($ctx.args.input.put("__typename", "Cow"))
$util.qr($ctx.args.input.put("PK01", "Cow#$ctx.args.input.farmId"))
$util.qr($ctx.args.input.put("SK01", $ctx.args.input.name))
$util.qr($ctx.args.input.put("PK02", "Animal#$ctx.args.input.farmId"))
$util.qr($ctx.args.input.put("SK02", $ctx.args.input.name))

{
    "version" : "2017-02-28",
    "operation" : "PutItem",
    "key" : {
        "PK": $util.dynamodb.toDynamoDBJson("NODE#$id"),
    },
    "attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;animals&lt;/code&gt; resolver request template on the &lt;code&gt;Farm&lt;/code&gt; type will look similar to &lt;code&gt;cows&lt;/code&gt; or &lt;code&gt;chickens&lt;/code&gt; but it will use the prefix &lt;code&gt;Animal#&lt;/code&gt; and the &lt;code&gt;GS02&lt;/code&gt; index.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "version" : "2017-02-28",
  "operation" : "Query",
  "index": "GS02",
  "query" : {
      "expression": "PK02 = :pk",
      "expressionValues" : {
        ":pk" : $util.dynamodb.toDynamoDBJson("Animal#$ctx.source.id")
      }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will return data for both &lt;code&gt;Cow&lt;/code&gt; and &lt;code&gt;Chicken&lt;/code&gt; types sorted by the animal name. AppSync can tell which type each record is by looking at the &lt;code&gt;__typename&lt;/code&gt; value that was written when it was created.&lt;/p&gt;

&lt;p&gt;Before finishing I want to share a couple of tricks that you might find helpful.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Storing the lower case value of a field in &lt;code&gt;SKxx&lt;/code&gt; gives case insensitive sorting while still being able to show the value as originally provided.&lt;/li&gt;
&lt;li&gt;Using a default value like &lt;code&gt;AAAAAAAAAAA&lt;/code&gt; or &lt;code&gt;ZZZZZZZZZZZ&lt;/code&gt; in &lt;code&gt;SKxx&lt;/code&gt; when a field is &lt;code&gt;null&lt;/code&gt; allows you to group entries without a value at the top or bottom of a list that is otherwise sorted.&lt;/li&gt;
&lt;li&gt;Adding prefixes to the &lt;code&gt;SKxx&lt;/code&gt; value can be used to group sorted results. An example of this would be prefixing the name of active users with &lt;code&gt;A&lt;/code&gt; and inactive with &lt;code&gt;I&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Conditionally writing attributes allows you to create sparse indexes when you want to exclude records from the result. An example of this might be using &lt;code&gt;GS01&lt;/code&gt; to access all cows that the farm has ever owned and &lt;code&gt;GS02&lt;/code&gt; to access only cows that are still here.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Many to Many Relationships
&lt;/h2&gt;

&lt;p&gt;Many to Many relationships with DynamoDB and AppSync can be problematic. Where possible you should avoid them. This can be done by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Denormalizing data. For example: Store the state and country name as part of an address record instead of storing ID's then using resolvers to look up those records.&lt;/li&gt;
&lt;li&gt;Perform the join in the application. For example: A CMS might have a many to many relationship between content and content types but it only has a small number of content types. You could load all of the content types through a list operation (using a DynamoDB &lt;code&gt;Query&lt;/code&gt;) then match the content to the content type in the application.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you cannot avoid many to many joins then options include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Using 1-M and M-1 relationships with an intermediate type (recommended by the Amplify team). This works but it should come with a &lt;strong&gt;big&lt;/strong&gt; warning because you are almost certainly introducing the N+1 problem. On the 1-M side you'll be using an efficient &lt;code&gt;Query&lt;/code&gt; operation but on the M-1 side you're probably performing M x &lt;code&gt;GetItem&lt;/code&gt; operations.&lt;/li&gt;
&lt;li&gt;If you only have a small number of connections between two items and you only access it in one direction then you might be able to use a DynamoDB list attribute with &lt;code&gt;BatchGetItem&lt;/code&gt;. Example: I might have an &lt;code&gt;Image&lt;/code&gt; type that stores a larger number of images. Each user needs to pick 3 images. In the DynamoDB record for the user I could store the selected images ID's in a list attribute.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Data Migrations
&lt;/h2&gt;

&lt;p&gt;As your GraphQL types change you will need to migrate data. Typically when this happens you need to find every record of a certain type then update it. Two hacks I've used to reduce the frequency of these updates are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Returning &lt;code&gt;null&lt;/code&gt; and forcing the client to deal with &lt;code&gt;null&lt;/code&gt; in a sensible way.&lt;/li&gt;
&lt;li&gt;Using the resolver response template to transform the DynamoDB data before sending it to the client.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At some point you're going to need to update data inside DynamoDB. To perform migrations I wrote a script that loads the entire table into memory as an array using the DynamoDB &lt;code&gt;Scan&lt;/code&gt; operation. It then looped over every record passing it to a migration function based on the &lt;code&gt;__typename&lt;/code&gt; attribute (if found). This function returns the DynamoDB operations that need to be performed to upgrade the record or &lt;code&gt;null&lt;/code&gt; if there are none.&lt;/p&gt;

&lt;p&gt;This is a simple solution but perfect for adding, removing and updating data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison to Multiple Table Design
&lt;/h2&gt;

&lt;p&gt;So how does this compare to a multiple table solution like Amplify?&lt;/p&gt;

&lt;p&gt;There's surprisingly very little difference.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The number and type of DynamoDB operations is almost the same. The only difference is that you should not &lt;code&gt;Scan&lt;/code&gt; a single table solution to return all records of one type.&lt;/li&gt;
&lt;li&gt;With on-demand capacity there is almost no price difference.&lt;/li&gt;
&lt;li&gt;Both are likely to require updating items when making changes to the schema and can use the hacks I suggested.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The big differences are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The number of tables you need to manage/monitor.&lt;/li&gt;
&lt;li&gt;Multiple return types work better with single table.&lt;/li&gt;
&lt;li&gt;Adding and removing DyanmoDB GSI's occurs less frequently with the single table design.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Can You Build a Better Single Table Solution?
&lt;/h2&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;This solution is designed to be generic and contains compromises to achieve that. By looking at your access patterns you can design a solution that is more efficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Next?
&lt;/h2&gt;

&lt;p&gt;I've already started work on version 2 that will include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Better support for many to many relations&lt;/li&gt;
&lt;li&gt;Delta Sync support&lt;/li&gt;
&lt;li&gt;Amplify data store support&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'm also working on a tool to generate a single table SAM backend.&lt;/p&gt;

&lt;p&gt;If you want to know more then subscribe to my mailing list below or over at &lt;a href="https://www.graphboss.com/"&gt;Graphboss&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>appsync</category>
      <category>aws</category>
      <category>dynamodb</category>
      <category>serverless</category>
    </item>
    <item>
      <title>How do I get my API Gateway URL?</title>
      <dc:creator>Rich</dc:creator>
      <pubDate>Fri, 07 Apr 2017 00:00:00 +0000</pubDate>
      <link>https://dev.to/richdevelops/how-do-i-get-my-api-gateway-url-2i9a</link>
      <guid>https://dev.to/richdevelops/how-do-i-get-my-api-gateway-url-2i9a</guid>
      <description>&lt;p&gt;Occasionally you need to know the API Gateway URL for your services inside your Lambda. This happened to me recently when one of my Lambda's needed to provide a callback URL to a third party service that it was using.&lt;/p&gt;

&lt;p&gt;It seems that a lot of people are solving this problem by deploying their API's using &lt;a href="https://serverless.com/"&gt;Serverless&lt;/a&gt;, then copying the URL and redeploying again with that URL hard coded into their &lt;code&gt;serverless.yml&lt;/code&gt; as an environment variable.&lt;/p&gt;

&lt;p&gt;If you're one of those people then &lt;strong&gt;STOP&lt;/strong&gt;. This is both the hard way and it has two very negative side effects.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It's difficult to deploy your application to other stages, and&lt;/li&gt;
&lt;li&gt;It introduces a risk that you'll break something by depolying to one stage with the API Gateway URL for the different stage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So how do I get around this?&lt;/p&gt;

&lt;p&gt;Instead of hard coding the API GW URL you can reconstruct it using a combination of Serverless variables and CloudFormation. Here's how I set the API gateway URL so it's passed to my Lambda as the GW_URL environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider:
  environment:
    GW_URL:
      Fn::Join:
        - ""
        - - "https://"
          - Ref: "ApiGatewayRestApi"
          - ".execute-api.${self:custom.region}.amazonaws.com/${self:custom.stage}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside my code I can now reference the current API GW URL using.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;process.env.GW_URL;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're using the &lt;a href="https://github.com/dherault/serverless-offline"&gt;serverless-offline plugin&lt;/a&gt; (or similar) then you will probably want to combine this with &lt;a href="//%7B%7B%20site.baseurl%20%7D%7D/blog/using-environment-variables-with-the-serverless-framework"&gt;per stage environment variables&lt;/a&gt;. You'll need to use per stage environment variables because this approach assumes you're deploying with CloudFormation but running it locally means no CloudFormation to resolve the URL. Instead you'll need to hard code a URL for local development.&lt;/p&gt;

&lt;p&gt;The "cheat sheet" version is below but I strongly recommend you read &lt;a href="//%7B%7B%20site.baseurl%20%7D%7D/blog/using-environment-variables-with-the-serverless-framework"&gt;the post&lt;/a&gt; as the full version explains it in more depth and covers topics like having separate files for each environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider:
  environment:
    GW_URL: ${self:provider.${self:custom.stage}.GW_URL}

custom:
  stage: "${opt:stage, self:provider.stage}"
  prod:
    GW_URL:
      {
        "Fn::Join":
          [
            "",
            [
              "https://",
              { "Ref": "ApiGatewayRestApi" },
              ".execute-api.${self:custom.region}.amazonaws.com/${self:custom.stage}",
            ],
          ],
      }
  dev:
    GW_URL: "http://localhost:3000/"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using this technique you can also export your API Gateway URL from your CloudFormation stack so that it can be imported into other stacks. This is one way to solve the service discovery problem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resources:
  Outputs:
    ApiUrl:
      Description: "The API Gateway URL"
      Value:
        Fn::Join:
          - ""
          - - "https://"
            - Ref: ApiGatewayRestApi
            - ".execute-api.${self:custom.region}.amazonaws.com/${self:custom.stage}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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