<?xml version="1.0" encoding="utf-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom">
  <title></title>
  <subtitle></subtitle>
  <id>https://www.endpointdev.com/blog/tags/serverless/</id>
  <link href="https://www.endpointdev.com/blog/tags/serverless/"/>
  <link href="https://www.endpointdev.com/blog/tags/serverless/" rel="self"/>
  <updated>2022-01-02T00:00:00+00:00</updated>
  <author>
    <name>End Point Dev</name>
  </author>
  
    <entry>
      <title>Generate PDF with Chrome, Puppeteer, and Serverless Stack</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/01/generate-pdf-serverless-stack-chrome-puppeteer/"/>
      <id>https://www.endpointdev.com/blog/2022/01/generate-pdf-serverless-stack-chrome-puppeteer/</id>
      <published>2022-01-02T00:00:00+00:00</published>
      <author>
        <name>Afif Sohaili</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/01/generate-pdf-serverless-stack-chrome-puppeteer/banner.jpg&#34; alt=&#34;Cloud Computing and Serverless&#34;&gt;&lt;/p&gt;
&lt;!-- Image from Pixabay, Pixabay license: https://pixabay.com/illustrations/cloud-network-website-computer-6515064/ --&gt;
&lt;p&gt;Function as a Service (FaaS) solutions are becoming more and more mainstream today. The FaaS model allows developers to not have to worry about managing infrastructure and instead focus on writing the application logic. In the FaaS model, developers write individual functions that run specific tasks that are deployed together on a FaaS platform, including but not limited to Amazon Web Services (AWS), Azure, and Google Cloud Platform (GCP).&lt;/p&gt;
&lt;p&gt;These functions don&amp;rsquo;t always run like a traditional application server does, waiting for a request. Instead, these FaaS platforms only spin up instances of these functions whenever there is traffic and will shut them back down once there are no more requests after a given period of time. This helps make FaaS a really cheap platform while traffic is low. It is a good approach for on-demand tasks that are part of the application but not necessarily the most common path in a customer&amp;rsquo;s everyday journey within the application.&lt;/p&gt;
&lt;h3 id=&#34;the-tools&#34;&gt;The tools&lt;/h3&gt;
&lt;p&gt;In this tutorial, we&amp;rsquo;re going to be looking at implementing PDF download with Chrome, Puppeteer, and Serverless Stack, but first, let&amp;rsquo;s have a brief introduction of the tools that we are going to be using.&lt;/p&gt;
&lt;h4 id=&#34;1-serverless-stack&#34;&gt;1. Serverless Stack&lt;/h4&gt;
&lt;p&gt;Serverless Stack is a framework for building full-stack serverless apps. The bigger player in this space is the Serverless framework. Serverless Stack is giving the Serverless framework (yes, I know, it can be very confusing at times) a challenge to the throne. The latter has been around for many years now and has been the authority for building serverless apps. There are pros and cons to both of them.&lt;/p&gt;
&lt;p&gt;Serverless Stack&amp;rsquo;s biggest advantage is the live Lambda debugging, a faster development process and overall better development experience. With the Serverless framework, developers have to either constantly deploy to the cloud to test any change, or spend some time setting up &lt;code&gt;serverless-offline&lt;/code&gt; for simulated Lambda and API gateway environments on their local machines.&lt;/p&gt;
&lt;p&gt;But with the Serverless framework, developers are not tied to using a specific platform, as it can be used to deploy functions on AWS, Azure, and Google Cloud Platform. Serverless Stack, on the other hand, only allows deploying to AWS since its internals are built on top of AWS CDK.&lt;/p&gt;
&lt;h4 id=&#34;2-chrome-and-puppeteer&#34;&gt;2. Chrome and Puppeteer&lt;/h4&gt;
&lt;p&gt;Chrome will be used in this tutorial as a headless browser, controlled using Puppeteer. Puppeteer is a Node.js package that provides an API to make Chrome or Chromium navigate to a specific URL, take screenshots, click buttons, type in input fields, as well as take PDF screenshots. Note that the PDF screenshot feature is specific to Chrome and not Chromium, so make sure to double check that you are using the right browser.&lt;/p&gt;
&lt;h3 id=&#34;implementing-pdf-screenshot-and-download-with-serverless-stack-chrome-and-puppeteer&#34;&gt;Implementing PDF screenshot and download with Serverless Stack, Chrome, and Puppeteer&lt;/h3&gt;
&lt;p&gt;Now, let&amp;rsquo;s get to the meat of this tutorial. We are going to implement PDF generation with Chrome and Puppeteer, deployed on AWS Lambda through Serverless Stack. Here is what we will do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use Puppeteer in Chrome to navigate to a receipt page that we implemented.&lt;/li&gt;
&lt;li&gt;Instruct Chrome to take a PDF screenshot of the page.&lt;/li&gt;
&lt;li&gt;Stream the PDF file back to the user.&lt;/li&gt;
&lt;li&gt;Deploy this implementation on AWS Lambda using Serverless Stack and demonstrate the development experience throughout.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;PDF generation is a great use case for FaaS. It represents something that doesn&amp;rsquo;t happen very often and is triggered on demand by the user. Instead of letting our monolith application handle that intermittent traffic, we can delegate the task to FaaS in the cloud which will do this job without worrying us about scaling the infrastructure to cater to this particular use case. Then our application can just focus on handling the regular web traffic for general use.&lt;/p&gt;
&lt;h4 id=&#34;prerequisite-sample-receipt-pages&#34;&gt;Prerequisite: Sample receipt pages&lt;/h4&gt;
&lt;p&gt;First, let&amp;rsquo;s create some demo pages for PDF screenshots. I&amp;rsquo;m not going to go in-depth on these since it&amp;rsquo;s not the topic of the day. I have deployed these simple HTML pages that are styled like receipts on this Surge instance &lt;a href=&#34;https://unwieldy-key.surge.sh/&#34;&gt;here&lt;/a&gt;. You can find the code on GitHub &lt;a href=&#34;https://github.com/afifsohaili-ep/receipt-demos&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;getting-started&#34;&gt;Getting started&lt;/h4&gt;
&lt;p&gt;Let&amp;rsquo;s generate a new Serverless Stack project.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ npx create-serverless-stack@latest pdf-generator
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ &lt;span style=&#34;color:#038&#34;&gt;cd&lt;/span&gt; pdf-generator
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ npm install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Congratulations, we have just created a new project with Serverless Stack! Here we get a basic template of a working Serverless Stack app. You can go &lt;a href=&#34;https://docs.serverless-stack.com/installation&#34;&gt;here&lt;/a&gt; for more information on how to get started with Serverless Stack.&lt;/p&gt;
&lt;p&gt;First, let&amp;rsquo;s look at the files we get from bootstrapping the project and customize them wherever we see fit.&lt;/p&gt;
&lt;h5 id=&#34;1-sstjson&#34;&gt;1. sst.json&lt;/h5&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;pdf-generator&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;region&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;us-east-1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;main&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;stacks/index.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This file is the entry point to the Serverless Stack app, and here we can define the region of choice, the name of the app, and the main file that Serverless Stack will use.&lt;/p&gt;
&lt;h5 id=&#34;2-stacksindexjs&#34;&gt;2. stacks/index.js&lt;/h5&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // stacks/index.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; app.setDefaultFunctionProps({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   runtime: &amp;#34;nodejs12.x&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-new MyStack(app, &amp;#34;my-stack&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+new MyStack(app, &amp;#34;pdf-generator&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;stacks/index.js&lt;/code&gt; is the main file declared in &lt;code&gt;sst.json&lt;/code&gt;. When building the project, Serverless Stack will use this file as an entry point to our application. This file is pretty simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It configures the Lambda functions to use the Node.js 12.x runtime.&lt;/li&gt;
&lt;li&gt;It registers a stack called &lt;code&gt;my-stack&lt;/code&gt;. This is the name of the CloudFormation stack on AWS. We definitely want to give it a better name than &lt;code&gt;my-stack&lt;/code&gt; here, so let&amp;rsquo;s rename that to &lt;code&gt;pdf-generator&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&#34;3-stacksmystackjs&#34;&gt;3. stacks/MyStack.js&lt;/h5&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; * as sst from &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;@serverless-stack/resources&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;default&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; MyStack &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;extends&lt;/span&gt; sst.Stack {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  constructor(scope, id, props) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;super&lt;/span&gt;(scope, id, props);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// Create a HTTP API
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; api = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; sst.Api(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;this&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Api&amp;#34;&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      routes: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;GET /&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;src/lambda.handler&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// Show the endpoint in the output
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;this&lt;/span&gt;.addOutputs({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;ApiEndpoint&amp;#34;&lt;/span&gt;: api.url,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;MyStack.js&lt;/code&gt; is where we declare the resources we need inside a given CloudFormation stack. This can be anything from a cluster of Lambda functions, API Gateway endpoints, DynamoDB tables, S3 buckets, etc. The full list of resources that we can create with Serverless Stack is listed &lt;a href=&#34;https://docs.serverless-stack.com/constructs/Api&#34;&gt;here in the documentation&lt;/a&gt;. For our project we just need one API endpoint with API gateway, so what we are provided with here is already sufficient.&lt;/p&gt;
&lt;h4 id=&#34;deploying-the-project&#34;&gt;Deploying the project&lt;/h4&gt;
&lt;p&gt;Now, we can try to deploy this project to AWS. Ensure that the AWS Access Key ID and AWS Secret Access Key are set in the development environment, and then run &lt;code&gt;npx sst start&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# export AWS credentials&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;=&amp;lt;access key id&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;=&amp;lt;secret access key&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# or if the credentials are set in ~/.aws/credentials&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;AWS_PROFILE&lt;/span&gt;=&amp;lt;AWS profile name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Start the app in development mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;npx sst start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After a few minutes, we will have the Cloudformation stack deployed on our AWS account. Keep in mind that the first deployment will take a bit longer than subsequent deployments. When the deployment is done, we should be able to see that it is watching for file changes from our local machine.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;threadlightly-pdf-generator-pdf-generator | CREATE_COMPLETE | AWS::ApiGatewayV2::Route | ApiRouteGET8AC7D3F8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;threadlightly-pdf-generator-pdf-generator | CREATE_COMPLETE | AWS::Lambda::Permission | ApiRouteGETthreadlightlypdfgeneratorpdfgeneratorApiRouteGET7230D6CDPermissionE4542537
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;threadlightly-pdf-generator-pdf-generator | CREATE_COMPLETE | AWS::CloudFormation::Stack | threadlightly-pdf-generator-pdf-generator
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; ✅  threadlightly-pdf-generator-pdf-generator
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Stack threadlightly-pdf-generator-pdf-generator
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Status: deployed
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Outputs:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ApiEndpoint: https://&amp;lt;yourapigatewayurl&amp;gt;.execute-api.us-east-1.amazonaws.com
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;==========================
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; Starting Live Lambda Dev
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;==========================
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Transpiling Lambda code...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Debug session started. Listening for requests...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let&amp;rsquo;s head to the API URL given in the output and we should get a response back like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! Your request was received at 30/Nov/2021:06:21:13 +0000.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;developing-with-serverless-stack&#34;&gt;Developing with Serverless Stack&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s try updating one of the files provided to see how we can test the changes we have made and will make.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // stacks/MyStack.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; const api = new sst.Api(this, &amp;#34;Api&amp;#34;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   routes: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    &amp;#34;GET /&amp;#34;: &amp;#34;src/lambda.handler&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    &amp;#34;GET /downloads/receipt&amp;#34;: &amp;#34;src/lambda.handler&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;   },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // stacks/index.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   runtime: &amp;#34;nodejs12.x&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-new MyStack(app, &amp;#34;my-stack&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+new MyStack(app, &amp;#34;pdf-generator&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here we did two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We updated the API endpoint to &lt;code&gt;GET /downloads/receipt&lt;/code&gt; instead of just &lt;code&gt;GET /&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We made the Lambda function return the URL given in the query string.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once we save these changes we should see Serverless Stack automatically reloading our code and since we changed the API endpoint path, Serverless Stack will need to make changes to our infrastructure (i.e. the API Gateway resources), and whenever it detects that it has to change the infrastructure of our application, it will immediately prompt us to redeploy.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Rebuilding code...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Rebuilding infrastructure...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Done building code
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Press ENTER to redeploy infrastructure&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once we hit ENTER, Serverless Stack will automatically update our infrastructure for us: delete our old endpoint, create a new endpoint on &lt;code&gt;GET /downloads/receipt&lt;/code&gt;, and hook up our &lt;code&gt;src/lambda.js&lt;/code&gt; file to handle that endpoint.&lt;/p&gt;
&lt;p&gt;When it&amp;rsquo;s ready, try hitting the API gateway URL again, but this time append &lt;code&gt;/downloads/receipt?url=https://google.com&lt;/code&gt; to it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// https://&amp;lt;yourapigatewayurl&amp;gt;.execute-api.us-east-1.amazonaws.com/downloads/receipt?url=https://google.com
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello! You&amp;#39;ve requested to print the receipt at page https://google.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Great, now we have learned how to make changes to our Serverless Stack application during development. Let&amp;rsquo;s go ahead and add Puppeteer and Chrome to our Lambda function.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // stacks/MyStack.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; import * as sst from &amp;#34;@serverless-stack/resources&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+import { LayerVersion } from &amp;#34;@aws-cdk/aws-lambda&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+const layerArn = &amp;#34;arn:aws:lambda:us-east-1:764866452798:layer:chrome-aws-lambda:25&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // Create a HTTP API
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; const api = new sst.Api(this, &amp;#34;Api&amp;#34;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   routes: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-     &amp;#34;GET /downloads/receipt&amp;#34;: &amp;#34;src/lambda.handler&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+     &amp;#34;GET /downloads/receipt&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+       function: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         handler: &amp;#34;src/lambda.handler&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         // Increase the timeout for generating screenshots
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         timeout: 15,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         // Load Chrome in a Layer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         layers: [LayerVersion.fromLayerVersionArn(this, &amp;#34;Layer&amp;#34;, layerArn)],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         // Exclude bundling it in the Lambda function
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         bundle: { externalModules: [&amp;#34;chrome-aws-lambda&amp;#34;] },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+       }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+     },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;   },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here, we are configuring our AWS Lambda function to use a Lambda layer that includes Chrome in our Lambda functions. You can think of these Lambda layers as being similar to NPM packages that you pull for extending your projects except that this is a 3rd party package for extending AWS Lambda functions instead.&lt;/p&gt;
&lt;p&gt;With this layer, your AWS Lambda function will boot with the Chrome binary already included. This layer is maintained &lt;a href=&#34;https://github.com/shelfio/chrome-aws-lambda-layer&#34;&gt;here&lt;/a&gt;. Make sure you&amp;rsquo;re using the ARN from the right region. In this tutorial, we are using &lt;code&gt;us-east-1&lt;/code&gt; per Serverless Stack&amp;rsquo;s default, so we are going to pick the ARN for &lt;code&gt;us-east-1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, let&amp;rsquo;s update our Lambda handler to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use Puppeteer to instruct Chrome to navigate to the URL passed in the parameter.&lt;/li&gt;
&lt;li&gt;Take a screenshot of the web page and save a PDF out of that. Puppeteer provides a handy &lt;code&gt;page.pdf&lt;/code&gt; API to do just that.&lt;/li&gt;
&lt;li&gt;Ultimately, stream that PDF file back to the user.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here&amp;rsquo;s the code for that, with some comments to help you out:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+import chrome from &amp;#34;chrome-aws-lambda&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+const puppeteer = chrome.puppeteer;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt; export async function handler(event) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-  return {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    statusCode: 200,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    headers: { &amp;#34;Content-Type&amp;#34;: &amp;#34;text/plain&amp;#34; },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    body: `Hello! You&amp;#39;ve requested to print the receipt at page ${event.queryStringParameters.url}`,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-  };
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  let browser
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  let response
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  try {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    const { url } = event.queryStringParameters;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    browser = await puppeteer.launch({
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      args: chrome.args,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      executablePath: await chrome.executablePath,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    const page = await browser.newPage();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    // Use A5 size at 150dpi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    const width = 874
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    const height = 1240
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    await page.setViewport({ width, height });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    // Navigate to the url
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    await page.goto(url, { waitUntil: &amp;#39;networkidle2&amp;#39; });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    // Take the screenshot
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    await page.pdf({path: &amp;#39;receipt.pdf&amp;#39;, width: width + &amp;#34;px&amp;#34;, height: height + &amp;#34;px&amp;#34;, printBackground: true});
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    response = {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      statusCode: 200,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      body: JSON.stringify({message: &amp;#39;Screenshot taken&amp;#39;})
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    };
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  } catch(err) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    response = {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      statusCode: 500,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      body: JSON.stringify({message: `An error occured. ${err.message}`})
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  } finally {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    await browser &amp;amp;&amp;amp; browser.close()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  return response
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let&amp;rsquo;s also not forget to install Puppeteer and Chrome.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ npm install puppeteer puppeteer-core chrome-aws-lambda&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, give Serverless Stack some time to reload our new changes. Once we have the API endpoint ready to go, let&amp;rsquo;s head to the browser and hit our API endpoint, passing the URL to the sample receipt page that we did earlier as a query param.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// Visit https://&amp;lt;yourapigatewayurl&amp;gt;.execute-api.us-east-1.amazonaws.com/downloads/receipt?url=https://unwieldy-key.surge.sh/index.html
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// Response:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&amp;#34;message&amp;#34;:&amp;#34;Screenshot taken&amp;#34;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Great! We are getting back a 200 status code and a message saying &amp;ldquo;Screenshot taken&amp;rdquo;. That means our code works and there were no exceptions. We&amp;rsquo;re halfway there.&lt;/p&gt;
&lt;p&gt;Now, if we check the project folder, we should now see a new file called &lt;code&gt;receipt.pdf&lt;/code&gt; there. This is the &lt;code&gt;receipt.pdf&lt;/code&gt; captured by Puppeteer. We can open this file and verify that this is the demo receipt page that I deployed earlier that we passed to the endpoint.&lt;/p&gt;
&lt;p&gt;We can also hit the API endpoint with the second sample receipt URL (i.e. &lt;code&gt;url=https://unwieldy-key.surge.sh/receipt-2.html&lt;/code&gt;) and we should see the &lt;code&gt;receipt.pdf&lt;/code&gt; file gets replaced with the receipt from the new URL.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/01/generate-pdf-serverless-stack-chrome-puppeteer/page-screenshot.png&#34; alt=&#34;receipt.pdf file that we get&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that in a real-world application, the URL you would want to screenshot might need some form of authentication, so you are going to have to figure that out for your application. In this case the sample receipt page is accessible publicly, so we don&amp;rsquo;t run into this problem, but chances are your users&amp;rsquo; receipt page will not be accessible publicly like this.&lt;/p&gt;
&lt;p&gt;One way you could solve this is to create a special user account that has permission to visit these authenticated pages for this purpose. Then, prior to visiting the receipt page, you&amp;rsquo;d program Puppeteer to first log in as the special user and then head to the receipt page URL to be screenshot.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;However, we are only halfway there. We can see the &lt;code&gt;receipt.pdf&lt;/code&gt; file now only because the &lt;code&gt;receipt.pdf&lt;/code&gt; file was made available to us by Serverless Stack, which automatically downloads that file to our local machine in development mode. In a real AWS Lambda execution, the file will remain in the filesystem of that Lambda execution context instead, and the user will not see it.&lt;/p&gt;
&lt;p&gt;Hence we have to read and stream that file back to the user instead of returning a JSON message saying &amp;ldquo;Screenshot taken&amp;rdquo;. Getting a 200 status code is a great start, but that still does not give our users the actual PDF that they wanted, so let&amp;rsquo;s update our Lambda handler to do just that:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // src/lambda.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+import * as fs from &amp;#34;fs&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // Take the screenshot
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; await page.pdf({path: &amp;#39;receipt.pdf&amp;#39;, width: width + &amp;#34;px&amp;#34;, height: height + &amp;#34;px&amp;#34;, printBackground: true});
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+const buffer = fs.readFileSync(&amp;#39;receipt.pdf&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; response = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   statusCode: 200,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-  body: JSON.stringify({message: &amp;#39;Screenshot taken&amp;#39;})
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  headers: {&amp;#39;Content-type&amp;#39; : &amp;#39;application/pdf&amp;#39;},
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  body: buffer.toString(&amp;#39;base64&amp;#39;),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  isBase64Encoded : true,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The changes this time are pretty simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Import the &lt;code&gt;fs&lt;/code&gt; module from Node.js, which will give us access to &lt;code&gt;readFileSync&lt;/code&gt; API that can read files on the disk.&lt;/li&gt;
&lt;li&gt;Read the generated &lt;code&gt;receipt.pdf&lt;/code&gt; file as a Buffer.&lt;/li&gt;
&lt;li&gt;Return the file&amp;rsquo;s Buffer as a base64 string in the response body.&lt;/li&gt;
&lt;li&gt;Set the content-type to be &lt;code&gt;application/pdf&lt;/code&gt;. Setting the content-type appropriately will tell the browser what to do with the file. Some browsers will display the PDF in an in-browser PDF reader, while others will download the file.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&amp;rsquo;s it! Now let&amp;rsquo;s test the change. Hit the same URL again.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/01/generate-pdf-serverless-stack-chrome-puppeteer/pdf-file.png&#34; alt=&#34;The PDF returned in a response, rendered in a PDF viewer in Edge&#34;&gt;&lt;/p&gt;
&lt;p&gt;Bingo, the endpoint now returns the PDF to the user! Now we can integrate this endpoint with our main application to delegate these on-demand PDF generation to managed FaaS infrastructure.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;FaaS platforms are a really great way to offload intermittent and on-demand traffic from our main application. This ensures our application runs smoother and can serve our general customers better, while at the same time handling these on-demand tasks really well.&lt;/p&gt;
&lt;h5 id=&#34;caveats&#34;&gt;Caveats&lt;/h5&gt;
&lt;p&gt;Building a full-blown application on the stack is also possible, but in my personal experience, it comes with its own complexities and tradeoffs that may or may not be worth the engineering time.&lt;/p&gt;
&lt;p&gt;For example, databases are an issue with going full-blown development on FaaS. Because FaaS can scale indefinitely, the number of connections to the database is a limitation.&lt;/p&gt;
&lt;p&gt;You can switch to FaaS-friendly databases such as DynamoDB or FaunaDB, but these are NoSQL databases and require a different mindset in structuring the application data than the simpler relational databases and that can cost tremendous engineering time. Relational databases are easier to work with for most types of applications.&lt;/p&gt;
&lt;p&gt;Nonetheless, more and more tools are being developed to tackle these complexities, such as &lt;a href=&#34;https://planetscale.com/&#34;&gt;PlanetScale&lt;/a&gt; and &lt;a href=&#34;https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-2.how-it-works.html&#34;&gt;Serverless Aurora v2&lt;/a&gt; (still in preview). All in all, it is definitely a trend worth following closely.&lt;/p&gt;
&lt;h3 id=&#34;further-readings&#34;&gt;Further readings&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Full code on GitHub: &lt;a href=&#34;https://github.com/afifsohaili-ep/serverless-stack-pdf-generator&#34;&gt;https://github.com/afifsohaili-ep/serverless-stack-pdf-generator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Code for the sample receipt pages: &lt;a href=&#34;https://github.com/afifsohaili-ep/receipt-demos&#34;&gt;https://github.com/afifsohaili-ep/receipt-demos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Serverless Stack: &lt;a href=&#34;https://serverless-stack.com/&#34;&gt;https://serverless-stack.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Serverless framework: &lt;a href=&#34;https://serverless.com/&#34;&gt;https://serverless.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.alldaydevops.com/blog/serverless-databases-the-good-the-bad-and-the-ugly&#34;&gt;Serverless Databases: The Good, the Bad, and the Ugly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Jamstack Conf Virtual 2020: Thoughts &amp; Highlights</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2020/06/jamstack-conf-virtual-may-2020/"/>
      <id>https://www.endpointdev.com/blog/2020/06/jamstack-conf-virtual-may-2020/</id>
      <published>2020-06-16T00:00:00+00:00</published>
      <author>
        <name>Greg Davidson</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2020/06/jamstack-conf-virtual-may-2020/conference.jpg&#34; alt=&#34;Conference&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;welcome-to-jamstack-conf-virtual-2020&#34;&gt;Welcome to Jamstack Conf Virtual 2020&lt;/h3&gt;
&lt;p&gt;Last week I attended &lt;a href=&#34;https://jamstackconf.com/2020/may/&#34;&gt;Jamstack Conf Virtual 2020&lt;/a&gt;. It had originally been slated to take place in London, UK but was later transformed into a virtual event in light of the COVID-19 pandemic. The conference began at 2pm London time (thankfully I double-checked this the night before!)—​6am for those of us in the Pacific Time Zone.&lt;/p&gt;
&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Up early for &lt;a href=&#34;https://twitter.com/hashtag/jamstackconf?src=hash&amp;amp;ref_src=twsrc%5Etfw&#34;&gt;#jamstackconf&lt;/a&gt; 😎☕️ &lt;a href=&#34;https://t.co/ydjvrHWCZH&#34;&gt;pic.twitter.com/ydjvrHWCZH&lt;/a&gt;&lt;/p&gt;— Greg Davidson (@syncopated) &lt;a href=&#34;https://twitter.com/syncopated/status/1265637434638778368?ref_src=twsrc%5Etfw&#34;&gt;May 27, 2020&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;
&lt;p&gt;Before getting too much further I wanted to mention that if you are not familiar with the Jamstack, You can read more about it at &lt;a href=&#34;https://jamstack.org/&#34;&gt;jamstack.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To virtually participate in the conference we used an app called &lt;a href=&#34;https://hopin.com/&#34;&gt;Hopin&lt;/a&gt;. I had not heard of it before but was impressed with how well it worked. There were over 3000 attendees from 130+ countries one of the times I checked. &lt;a href=&#34;https://www.hawksworx.com/&#34;&gt;Phil Hawksworth&lt;/a&gt; was the Host/​MC for the event and did a great job. There were virtual spaces for the stage, sessions, expo (vendors), and networking. If you opted to, the networking feature paired you with a random attendee for a video chat. I’m not sure what I expected going into it but I thought it was fun. I met a fellow developer from the Dominican Republic. The experience was very similar though more serendipitous than the hallway track or lunch line at an in-person conference.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/06/jamstack-conf-virtual-may-2020/phil-welcome.png&#34; alt=&#34;Phil Hawksworth welcoming the attendees&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;keynote&#34;&gt;Keynote&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/biilmann&#34;&gt;Matt Biilmann&lt;/a&gt; opened the conference with a keynote address about the challenges we face as a developer community trying to improve access to accurate, timely and locally relevant information to a global audience. Many billions of users with all kinds of devices and varying levels of connectivity. He moved on to share how Netlify is trying to enable developers to “build websites instead of infrastructure” and “ensure all the best practices become common practices” through features like git-based deployments, build plugins, and edge handlers (more on those later).&lt;/p&gt;
&lt;h3 id=&#34;state-of-the-jamstack-survey-results&#34;&gt;State of the Jamstack Survey results&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/06/jamstack-conf-virtual-may-2020/laurie-voss-talk.png&#34; alt=&#34;Laurie Voss reporting findings from the Jamstack Survey 2020&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://seldo.com/&#34;&gt;Laurie Voss&lt;/a&gt; &lt;a href=&#34;https://slides.com/seldo/jamstack-survey-2020#/&#34;&gt;walked us through&lt;/a&gt; the results of the &lt;a href=&#34;https://www.netlify.com/blog/2020/05/27/state-of-the-jamstack-survey-2020-first-results/&#34;&gt;Jamstack Survey 2020&lt;/a&gt;. There were some interesting findings and surprises. Later on I read &lt;a href=&#34;https://seldo.com/posts/you-will-never-be-a-full-stack-developer/&#34;&gt;Laurie’s post&lt;/a&gt; (which Matt had mentioned in his talk) and found that very interesting as well.&lt;/p&gt;
&lt;h3 id=&#34;fireside-chat-with-harper-reed&#34;&gt;Fireside chat with Harper Reed&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/06/jamstack-conf-virtual-may-2020/harper-phae-talk.png&#34; alt=&#34;Frances Berriman interviewed Harper Reed - fireside chat style&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://fberriman.com/&#34;&gt;Frances Berriman&lt;/a&gt; chatted with &lt;a href=&#34;https://harperreed.com/&#34;&gt;Harper Reed&lt;/a&gt; and asked him about his application of Jamstack principles from years ago when he led the technology team for Barack Obama’s election campaign. He described the need to “get far with very limited resources” and spoke about experimenting with serving HTML from Google App Engine and Amazon S3. Using pre-built HTML allowed them to scale very efficiently and he opted to use message queues rather than interacting with the database to keep things very quick for users.&lt;/p&gt;
&lt;p&gt;Another benefit Harper noted was how quickly and easily new team members could be onboarded. Folks who knew HTML, CSS and JavaScript would be up to speed and productive in no time. He admitted it’s more complicated today 😀. Speed is another benefit—he just loves when he comes across a super-fast web site (often mostly static).&lt;/p&gt;
&lt;h3 id=&#34;lightning-launch-netlify-&#34;&gt;Lightning Launch: Netlify ⚡&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/calavera&#34;&gt;David Calavera&lt;/a&gt; gave a demo of Netlify’s new Edge Handlers feature which lets developers add logic to their code at the edge (e.g. the CDN servers geographically closest to the user). He demonstrated how Edge Handlers make it possible to examine the headers of the request to tailor the response to that unique request. Check out the &lt;a href=&#34;https://www.youtube.com/watch?v=D44n8YVb5iI&#34;&gt;video of his talk&lt;/a&gt; to watch him live code an example. I believe &lt;a href=&#34;https://workers.cloudflare.com/&#34;&gt;Cloudflare Workers&lt;/a&gt; and Fastly’s &lt;a href=&#34;https://www.fastly.com/products/edge-compute/serverless&#34;&gt;Edge Compute&lt;/a&gt; are operating in a similar problem space. I plan to look into each of these offerings more thoroughly in future.&lt;/p&gt;
&lt;h3 id=&#34;lightning-launch-prismic-&#34;&gt;Lightning Launch: Prismic ⚡&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/RenaudBressand&#34;&gt;Renaud Bressand&lt;/a&gt; from Prismic demoed &lt;a href=&#34;https://www.slicemachine.dev/&#34;&gt;Slicemachine&lt;/a&gt;—​a new feature from Prismic which lets you combine &lt;a href=&#34;https://nuxtjs.org/&#34;&gt;nuxt&lt;/a&gt;/​Vue components with content managed in Prismic. It looked like a very compelling way of enabling better collaboration between developers and content creators.&lt;/p&gt;
&lt;h3 id=&#34;lightning-launch-redwoodjs-&#34;&gt;Lightning Launch: RedwoodJS ⚡&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://tom.preston-werner.com/&#34;&gt;Tom Preston-Werner&lt;/a&gt; demoed his latest project &lt;a href=&#34;https://redwoodjs.com/&#34;&gt;RedwoodJS&lt;/a&gt;. I had heard Tom speak about this on a few podcasts recently and it was nice to see him demo it for us. It looks interesting and feels reminiscent of &lt;a href=&#34;https://rubyonrails.org/&#34;&gt;Rails&lt;/a&gt;. RedwoodJS simplifies wiring up your database to a &lt;a href=&#34;https://graphql.org/&#34;&gt;GraphQL&lt;/a&gt; API (they use &lt;a href=&#34;https://www.prisma.io/&#34;&gt;Prisma&lt;/a&gt; for this) and integrating it into a React application. Tom is also the creator of &lt;a href=&#34;https://jekyllrb.com/&#34;&gt;Jekyll&lt;/a&gt;—​a Jamstack-style tool which has been around for many years. It was nice to see several speakers give him some recognition for his work on that project.&lt;/p&gt;
&lt;h3 id=&#34;the-covid-tracking-project-0-to-2m-api-requests-in-3-months&#34;&gt;The COVID Tracking Project: 0 to 2M API requests in 3 months&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/06/jamstack-conf-virtual-may-2020/kissane.png&#34; alt=&#34;Erin Kissane presenting The COVID Tracking Project&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://incisive.nu/&#34;&gt;Erin Kissane&lt;/a&gt; gave an inspiring talk about her work on &lt;a href=&#34;https://covidtracking.com&#34;&gt;The COVID Tracking Project&lt;/a&gt;. She described it as an “interim public data rescue project”. Erin and some friends, journalists and volunteers worked together to create the site. They started by scraping COVID data from each state’s web site and storing it in a Google Sheet. They used &lt;a href=&#34;https://www.gatsbyjs.com/&#34;&gt;Gatsby&lt;/a&gt;, &lt;a href=&#34;https://www.contentful.com/&#34;&gt;Contentful&lt;/a&gt;, and &lt;a href=&#34;https://css-tricks.com/introducing-sass-modules/&#34;&gt;Sass modules&lt;/a&gt; and hosted the site on Netlify. Using the Jamstack approach allowed the site to remain performant and continue to be responsive under some huge traffic spikes. Over time, they iterated on the design and continue to improve the site daily. I highly recommend checking out the &lt;a href=&#34;https://www.youtube.com/watch?v=ryngYoHXNfQ&#34;&gt;video of the talk&lt;/a&gt; when you get a chance.&lt;/p&gt;
&lt;h3 id=&#34;jamstack-for-emerging-markets&#34;&gt;Jamstack for emerging markets&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://www.codebeast.dev/&#34;&gt;Christian Nwamba&lt;/a&gt; described some of the challenges of building sites for users in Nigeria (low power devices, spotty connectivity, unreliable power). He shared that 55% of the most visited sites in Nigeria are global companies (Google, Facebook/​Instagram, Netflix, Stack Overflow etc.). Christian reviewed a large, popular banking site in Nigeria and noted its many shortcomings.&lt;/p&gt;
&lt;p&gt;To demonstrate how the bank might do better he built &lt;a href=&#34;https://proud-flower-060c1e01e.azurestaticapps.net/&#34;&gt;an app&lt;/a&gt; for transferring money between friends &amp;amp; family built in the Jamstack style and using serverless functions. The most interesting thing I picked up from this was his method of using serverless functions to protect the app secrets (API keys, etc.). The front end of the application did not need to concern itself with this—​the serverless functions kept the secrets safe and acted as a proxy between the frontend and the backend APIs. Be sure to take a look at &lt;a href=&#34;https://github.com/christiannwamba/quickbank&#34;&gt;Christian’s code&lt;/a&gt; if you are interested.&lt;/p&gt;
&lt;h3 id=&#34;managing-diabetes-with-jamstack&#34;&gt;Managing diabetes with Jamstack&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://jamiebradley.dev/&#34;&gt;Jamie Bradley&lt;/a&gt; taught us about diabetes and the Jamstack app he built to help himself and others manage it. He built &lt;a href=&#34;https://heysugar.health/&#34;&gt;HeySugar&lt;/a&gt; with Gatsby and Sanity, hosted it on Netlify. He’s making it easier for others to deploy their own instances as well.&lt;/p&gt;
&lt;h3 id=&#34;selling-tickets-without-servers-or-frameworks-or-cookies&#34;&gt;Selling tickets without servers. Or frameworks. Or cookies.&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://jvhellemond.nl/&#34;&gt;Jan van Hellemond&lt;/a&gt; has volunteered for years at a very popular conference in Europe (&lt;a href=&#34;https://fronteers.nl/congres&#34;&gt;Fronteers&lt;/a&gt; I think). In previous years tickets sold out in 6 minutes one year, and 30 seconds in another! Their ticket vendor was struggling to handle to the load and this caused them to oversell early bird tickets. Jan built a Jamstack site to sell their tickets and was very pleased with the performance. He used simple, single-purpose vanilla JavaScript components and simple, single-purpose API handlers (serverless functions).&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/06/jamstack-conf-virtual-may-2020/tickets.png&#34; alt=&#34;Jan presenting his Jamstack ticket selling app&#34;&gt;&lt;/p&gt;
&lt;p&gt;Jan prerendered as much as possible and seeded a relational database with the tickets available for sale. As the tickets were sold, they were marked sold with a database update. Webhooks were used as customers stepped through the checkout flow. Jan joked about deploying to production on a Friday afternoon because of how safe and simple the deployment process was. There were no services to restart, etc. because “it’s just files”. It was cool to see a practical example and that Jan used the basic building blocks of the web (HTML, JavaScript, CSS, old school links) successfully without reaching for a large JavaScript framework.&lt;/p&gt;
&lt;h3 id=&#34;the-business-side-of-the-jamstack&#34;&gt;The business side of the Jamstack&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/_anarossetto_&#34;&gt;Ana Rossetto&lt;/a&gt; shared how her company has been having great success with the Jamstack. Previously, the agency had been building projects for clients with Drupal. She walked through a project she and the team built to encourage people to buy from small, locally-owned businesses. She was impressed with what they were able to create in a very short amount of time.&lt;/p&gt;
&lt;h3 id=&#34;build-plugin-authors-session&#34;&gt;Build plugin authors’ session&lt;/h3&gt;
&lt;p&gt;After the main talks there were several sessions. In the Hopin app, I was able to peek into some of these and the presenters and attendees were chatting (both text chat and video). This was very much like the experience of peeking into conference rooms at a physical venue and choosing whether to stay and participate or move on. After some wandering I chose to attend a session with three Netlify build plugin authors.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/06/jamstack-conf-virtual-may-2020/subfont.png&#34; alt=&#34;Peter telling us about subfont&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://mntr.dk/&#34;&gt;Peter Müller&lt;/a&gt; built &lt;a href=&#34;https://github.com/Munter/netlify-plugin-subfont&#34;&gt;Subfont&lt;/a&gt; with a friend. He demonstrated how subsetting web fonts (i.e. only loading the characters &amp;amp; glyphs you really need) can dramatically improve frontend performance. He compared the &lt;a href=&#34;https://webpagetest.org/&#34;&gt;WebPageTest&lt;/a&gt; results for Google Fonts with and without Subfont and Subfont was seconds faster! I have subset and locally hosted webfonts in several client projects here at End Point. It takes time and is a manual process. Peter’s plugin makes this excellent performance improvement relatively painless.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://darn.es/&#34;&gt;David Darnes&lt;/a&gt; demoed his build plugin to &lt;a href=&#34;https://github.com/daviddarnes/netlify-plugin-ghost-markdown&#34;&gt;turn Ghost content into Markdown files&lt;/a&gt;. Very interesting and it seemed flexible enough to work with other tools as well.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/bahmutov&#34;&gt;Gleb Bahmutov&lt;/a&gt; presented the build plugin he created to run &lt;a href=&#34;https://www.cypress.io/&#34;&gt;Cypress&lt;/a&gt; tests as part of your Netlify build process. It was amazing to see how simple it was (single devDependency and single line in the netlify.toml config file).&lt;/p&gt;
&lt;h3 id=&#34;videos-from-the-conference&#34;&gt;Videos from the conference&lt;/h3&gt;
&lt;p&gt;Netlify has already put all of the &lt;a href=&#34;https://www.youtube.com/playlist?list=PL58Wk5g77lF8jzqp_1cViDf-WilJsAvqT&#34;&gt;Jamstack Conf Virtual 2020 talks&lt;/a&gt; on YouTube so head over there and check those out if you’d like to. Thanks very much to the team at Netlify for organizing the conf, all of the speakers, vendors and attendees!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Facial Recognition Using Amazon DeepLens: Counting Liquid Galaxy Interactions</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2019/05/facial-recognition-amazon-deeplens/"/>
      <id>https://www.endpointdev.com/blog/2019/05/facial-recognition-amazon-deeplens/</id>
      <published>2019-05-01T00:00:00+00:00</published>
      <author>
        <name>Ben Ironside Goldstein</name>
      </author>
      <content type="html">
        &lt;p&gt;I have been exploring the possible uses of a machine-learning-enabled camera for the Liquid Galaxy. The Amazon Web Services (AWS) &lt;a href=&#34;https://aws.amazon.com/deeplens/&#34;&gt;DeepLens&lt;/a&gt; is a camera that can receive and transmit data over wifi, and that has computing hardware built in. Since its hardware enables it to use machine learning models, it can perform computer vision tasks in the field.&lt;/p&gt;
&lt;h3 id=&#34;the-amazon-deeplens-camera&#34;&gt;The Amazon DeepLens camera&lt;/h3&gt;
&lt;img style=&#34;float: left; width: 400px; padding-right: 2em;&#34; src=&#34;/blog/2019/05/facial-recognition-amazon-deeplens/deeplens-front-angle.jpg&#34; alt=&#34;DeepLens&#34; /&gt;
&lt;p&gt;This camera is the first of its kind—likely the first of many, given the ongoing rapid adoption of Internet of Things (IoT) devices and computer vision. It came to End Point’s attention as hardware that could potentially interface with and extend End Point’s immersive visualization platform, the &lt;a href=&#34;https://www.visionport.com/&#34;&gt;Liquid Galaxy&lt;/a&gt;. We’ve thought of several ways computer vision could potentially work to enhance the platform, for example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Monitoring users’ reactions&lt;/li&gt;
&lt;li&gt;Counting unique visitors to the LG&lt;/li&gt;
&lt;li&gt;Counting the number of people using an LG at a given time&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first idea would depend on parsing facial expressions. Perhaps a certain moment in a user experience causes people to look confused, or particularly delighted—valuable insights. The second idea would generate data that could help us assess the platform’s impact, using a metric crucial to any potential clients whose goals involve engaging audiences. The third idea would create a simpler metric: the average number of people engaging with the system over a period of time. Nevertheless, this idea has a key advantage over the second: it doesn’t require distinguishing between people, which makes it a much more tractable project. This post focuses on the third idea.&lt;/p&gt;
&lt;p&gt;To set up the camera, the user has to plug it into a power outlet and connect it to wifi. The camera will still work even with a slow network connection, though when the connection is slower the delay between the camera seeing something and reporting it is longer. However, this delay was hardly noticable on my home network which has slow-to-moderate speeds of about 17 Mbps down and 33 Mbps up.&lt;/p&gt;
&lt;h3 id=&#34;computer-vision-and-the-amazon-deeplens&#34;&gt;Computer Vision and the Amazon DeepLens&lt;/h3&gt;
&lt;p&gt;A &lt;a href=&#34;https://en.wikipedia.org/wiki/Deep_learning&#34;&gt;deep learning model&lt;/a&gt; is a neural network with multiple layers of processing units. It is called “deep” because it has multiple layers. The inputs and outputs of each processing unit are numbers. These units are roughly analogous to neurons: they receive input from units in the previous layer, and output it to units in the next layer after transforming it based on a function. These “activation functions” can change in a variety of ways. The last layer’s outputs translate into the results. These models work because these functions get tuned based on how well the model works. For example, to make a model that labels each human face in a picture and draws a box around it, we would start with a corpus of pictures with boxes drawn around faces, as well as the versions of the pictures without the boxes drawn. We would test the model on the non-labeled images by checking—for each picture—whether the output generated by the model is correct. If not, the computer chooses different unit functions, tries again, and compares the results. Repeating this process thousands of times yields models which work remarkably well for a wide range of tasks, including computer vision.&lt;/p&gt;
&lt;p&gt;In deep learning for computer vision, training on large sets of labeled images enables models to generalize about visual characteristics. The training process takes a lot of computing resources, but once models are trained, they can produce results quickly and with relative ease. This is why the DeepLens is able to perform computer vision with its limited computing resources.&lt;/p&gt;
&lt;p&gt;Since the DeepLens is an Amazon product, it comes as no surprise that the user interface and backend for DeepLens consist of AWS services. One of the most important is &lt;a href=&#34;https://aws.amazon.com/sagemaker/&#34;&gt;SageMaker&lt;/a&gt;, which is used to train, manage, optimize, and deploy machine learning models such as neural networks. It includes hosted Jupyter notebooks (&lt;a href=&#34;https://jupyter.org/&#34;&gt;Jupyter&lt;/a&gt; is a development environment for data science), as well as the computing resources required for model training and storage. With SageMaker, users can train computer vision models for deployment to DeepLens, or import and adjust pretrained models from various sources.&lt;/p&gt;
&lt;p&gt;Remote management of the DeepLens depends on &lt;a href=&#34;https://aws.amazon.com/lambda/&#34;&gt;AWS Lambda&lt;/a&gt;, a “serverless” cloud service that provides an environment to run backend code and integrate with other cloud services. It runs the show, allowing users to manage everything from the camera’s behavior to what happens to gathered data. Another service, &lt;a href=&#34;https://aws.amazon.com/greengrass/&#34;&gt;AWS Greengrass&lt;/a&gt;, connects the instructions from AWS Lambda to the DeepLens, managing tasks like authentication, updates, and reactions to local events.&lt;/p&gt;
&lt;p&gt;Amazon’s IoT service saves information about each DeepLens, and allows users to manage their devices, for example by choosing which model is active on the device, or viewing a live stream from the camera. It also keeps track of what’s going on with the hardware, even when it’s off. When a model is running on the DeepLens, you can view a live stream of its inferences about what it’s seeing (the labeled images). Amazon has released various pretrained models designed to work on the DeepLens. Using a model for detecting faces, we can get a live stream that looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/05/facial-recognition-amazon-deeplens/one-face-recognition.jpg&#34; alt=&#34;one-face-recognition&#34;&gt;
&lt;br&gt;Me looking at the DeepLens in my kitchen&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/05/facial-recognition-amazon-deeplens/multi-face-recognition.jpg&#34; alt=&#34;multi-face-recognition&#34;&gt;
&lt;br&gt;Facial recognition inferences on multiple people. (Witness my smile of satisfaction at finally finding enthusiastic subjects of facial recognition.)&lt;/p&gt;
&lt;p&gt;Each face that the camera detects gets a box around it, along with the model’s level of certainty that it is a face. The above pictures were the results of an attempt to simulate the conditions where this could be used.&lt;/p&gt;
&lt;h3 id=&#34;the-model&#34;&gt;The Model&lt;/h3&gt;
&lt;p&gt;The model I used was trained on data from &lt;a href=&#34;http://www.image-net.org/&#34;&gt;ImageNet&lt;/a&gt;, a public database with hundreds or thousands of images associated with nouns. (For example they have 1537 &lt;a href=&#34;http://www.image-net.org/synset?wnid=n03376595&#34;&gt;pictures of folding chairs&lt;/a&gt;.) ImageNet is &lt;a href=&#34;https://arxiv.org/search/?query=imagenet&amp;amp;searchtype=all&amp;amp;source=header&#34;&gt;commonly&lt;/a&gt; used to train and test computer vision models.&lt;/p&gt;
&lt;p&gt;However, the training for this model didn’t stop there: Amazon used transfer learning from another large image dataset, &lt;a href=&#34;http://cocodataset.org/#home&#34;&gt;MS-COCO&lt;/a&gt;, to fine-tune the model for face detection. Transfer learning works essentially by retraining the last layer of an already-trained model. In this way it harnesses the “insights” of the existing model (e.g. about shapes, colors, and positions) by repurposing this information to make predictions about something else. In this case, whether something is a face.&lt;/p&gt;
&lt;p&gt;Since this model was pretrained and optimized by Amazon for the DeepLens, it provides a low effort route to implementing a computer vision model on the DeepLens. I didn’t have to do any of the processing on my own hardware. The DeepLens hardware took care of all the predictions, though the biggest resource savings were from not having to train the model myself (which can take days, or longer).&lt;/p&gt;
&lt;p&gt;When the facial recognition model is deployed and the DeepLens is on, an AWS Lambda function written in Python repeatedly prompts the camera to get frames from the camera:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;frame = awscam.getLastFrame()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&amp;hellip;to resize the frames before inference (the model accepts frames of particular size):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;frame_resize = cv2.resize(frame, (input_height, input_width))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&amp;hellip;to pass the frames to the model:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;parsed_inference_results = model.parseResult(model_type, model.doInference(frame_resize))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&amp;hellip;and to use the results to draw boxes around the faces:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), (&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;255&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;165&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;20&lt;/span&gt;), &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;10&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can see from how often “cv2” appears in the code above, this implementation relies heavily on code from &lt;a href=&#34;https://opencv.org&#34;&gt;OpenCV&lt;/a&gt;, an open source computer vision framework. Finally, the results are sent to the cloud:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;client.publish(topic=iot_topic, payload=json.dumps(cloud_output))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the last code snippet above, iot_topic refers to an Amazon “MQTT topic” (Message Queuing Telemetry Transport), for IoT devices. &lt;a href=&#34;https://en.wikipedia.org/wiki/MQTT&#34;&gt;MQTT&lt;/a&gt; is the standard connectivity framework for DeepLens and many other IoT devices. One of its advantages for this context is that it can handle situations with intermittent connectivity, by smoothly queueing messages for when the network connection is stable. The essence of MQTT is to enable publishing and subscribing to different topics. The system of topics enables results from a DeepLens to trigger other processes. For example, the DeepLens could publish a message when it sees a face, and this could prompt another cloud service to do something else, such as save what time and how long the face appeared.&lt;/p&gt;
&lt;p&gt;I wanted to test how data from this model would compare to a human’s perception. The first step was to understand what data the camera offers. It produces data about each frame analyzed: a timestamp (in 13-digit &lt;a href=&#34;https://en.wikipedia.org/wiki/Unix_time&#34;&gt;Unix time&lt;/a&gt;), and the predicted probability that something it identifies is a face. To gather this data, I used the AWS IoT service to manually subscribe to a secure MQTT topic where the DeepLens published its predictions. Each frame processed produces data like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;format&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;json&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;payload&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;face&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.5654296875&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;qos&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1554853281975&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;topic&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;$aws/things/deeplens_bnU5sr2sSD2ecW5YkfJZtw/infer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The data generated by a single frame (with one face) when processed by the DeepLens.&lt;/p&gt;
&lt;p&gt;For my purposes, I was only interested in the timestamps and payloads (which contain the number of faces identified, and their probabilities). I decided to test the facial recognition model under several different conditions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;No faces present&lt;/li&gt;
&lt;li&gt;One face present&lt;/li&gt;
&lt;li&gt;Multiple faces present&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For condition 1 I just aimed it at an empty room for 20 minutes, and for condition 2 I sat in front of the camera for 20 minutes. For condition 3, I aimed the camera at a public space for 20 minutes, and while it was running I kept an ongoing count of the number of people looking in the general direction of the camera (I put the camera in front of a wall with a TV on it so people would be more likely to look towards it). Then I averaged my count over the duration of the sample, which resulted in an average engagement number of 2.5 people, meaning that on average, 2.5 people were looking at the camera. In an attempt to minimize bias, I made my human-eye assessment before looking at any of the data.&lt;/p&gt;
&lt;p&gt;I’ll spoil one aspect of the results right away: there were no false positives under any condition. Even the lower probability guesses corresponded to actual faces, though this result might not hold true in a room with lots of face-like art, that’s not too common of a scenario. This simplified things, since it meant there was no need to set a lower bound on the probabilities which we should count—any face detected by the camera is a face. This also highlights one of my remaining questions about the model: is there useful information to be gained from the probabilities?&lt;/p&gt;
&lt;p&gt;Another important note: I noticed early in the experiment that it almost never detects a face farther than 15 feet away. For the use case of a Liquid Galaxy, the 15-foot range is too short to capture all types of engagement (some people look at it across the room), but from my experience with the system I think that users within this range could be accurately described as focused users—something worth measuring, but certainly not everything worth measuring. After noticing this, I retested condition 2 with my face about 5 feet from the DeepLens, after initially trying it from across a room.&lt;/p&gt;
&lt;h3 id=&#34;how-did-the-deeplens-counts-compare-to-my-counts&#34;&gt;How did the DeepLens counts compare to my counts?&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/05/facial-recognition-amazon-deeplens/results.png&#34; alt=&#34;results&#34;&gt;&lt;/p&gt;
&lt;p&gt;The model matched my performance in conditions 1 and 2, which makes a strong statement about its reliability in relatively static and close-up conditions such as looking at an empty room, or looking at someone stare at their laptop across a small table. In contrast, it did not count as many faces as I did in condition 3—so I’m happy to report I can still outperform A.I. on something.&lt;/p&gt;
&lt;p&gt;Anyway, this suggests that the model is somewhat conservative, at least compared to my count (likely partly due to my eyes having a range larger than 15 feet). Therefore, when considering usage statistics gathered by a similar method, it might make most sense to think of the results as a lower bound, e.g. “the average number of people focused on the system was more than 2.1”.&lt;/p&gt;
&lt;p&gt;It would be useful to experiment with the multiple faces condition again, to see how robust these findings are. It would also be helpful to keep track of factors like how much people move, the lighting, and the orientation of the camera, to see if they might impact the results. It would also be useful to automate the data collection and analysis.&lt;/p&gt;
&lt;p&gt;This investigation has showed me that the DeepLens has a lot of potential as a tool for measuring engagement. Perhaps a future post will examine how it can be used to count users.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Thanks for reading! You are welcome to learn more about &lt;a href=&#34;https://www.visionport.com/&#34;&gt;End Point Liquid Galaxy&lt;/a&gt; and &lt;a href=&#34;https://aws.amazon.com/deeplens/&#34;&gt;AWS DeepLens&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
  
</feed>
