<?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: PatrickWeaver</title>
    <description>The latest articles on DEV Community by PatrickWeaver (@patrickweaver).</description>
    <link>https://dev.to/patrickweaver</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%2F68310%2Fcecc96b2-e373-4ca4-b56f-e750c28d9c27.jpeg</url>
      <title>DEV Community: PatrickWeaver</title>
      <link>https://dev.to/patrickweaver</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/patrickweaver"/>
    <language>en</language>
    <item>
      <title>Installing Node 12 and higher on a Raspberry Pi Zero with nvm</title>
      <dc:creator>PatrickWeaver</dc:creator>
      <pubDate>Wed, 04 Aug 2021 00:15:54 +0000</pubDate>
      <link>https://dev.to/patrickweaver/installing-node-12-and-higher-on-a-raspberry-pi-zero-with-nvm-4dnj</link>
      <guid>https://dev.to/patrickweaver/installing-node-12-and-higher-on-a-raspberry-pi-zero-with-nvm-4dnj</guid>
      <description>&lt;p&gt;I usually use &lt;a href="https://github.com/nvm-sh/nvm" rel="noopener noreferrer"&gt;nvm&lt;/a&gt; to manage node.js versions for projects, but there are not official binaries for the ARM V6 chip in a Raspberry Pi Zero for node versions 12 and higher. The source is still available, and so if you try &lt;code&gt;nvm install 14.17.4&lt;/code&gt; nvm will attempt to compile from source, which on a Raspberry Pi zero will take a long, long, long time (and probably fail).&lt;/p&gt;

&lt;p&gt;Fortunately node.js provides "unofficial builds" of newer node versions for ARM v6 at &lt;a href="https://unofficial-builds.nodejs.org/" rel="noopener noreferrer"&gt;unofficial-builds.nodejs.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can use these binaries with nvm by providing a url to use instead of the default node.js binaries url:&lt;/p&gt;

&lt;h2&gt;
  
  
  nodejs 14
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NVM_NODEJS_ORG_MIRROR=https://unofficial-builds.nodejs.org/download/release nvm install 14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  nodejs 16
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NVM_NODEJS_ORG_MIRROR=https://unofficial-builds.nodejs.org/download/release nvm install 16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>raspberrypi</category>
      <category>node</category>
      <category>nvm</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to Download an Image from a Google Doc</title>
      <dc:creator>PatrickWeaver</dc:creator>
      <pubDate>Thu, 16 Jul 2020 14:30:30 +0000</pubDate>
      <link>https://dev.to/patrickweaver/how-to-download-an-image-from-a-google-doc-4gf9</link>
      <guid>https://dev.to/patrickweaver/how-to-download-an-image-from-a-google-doc-4gf9</guid>
      <description>&lt;p&gt;For some reason Google hasn't built in a way for you to download images in Google docs! There are workarounds to get those image files like &lt;a href="https://twitter.com/corduroy/status/1184758335934849025" rel="noopener noreferrer"&gt;using Google Keep&lt;/a&gt;, or &lt;a href="https://twitter.com/tonyvincent/status/1021726699178708993" rel="noopener noreferrer"&gt;downloading your whole doc as a .zip file&lt;/a&gt;, but these have always felt like too many steps.&lt;/p&gt;

&lt;p&gt;And this is something that people really want!&lt;/p&gt;

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

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



&lt;/p&gt;

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

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



&lt;/p&gt;

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

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



&lt;/p&gt;

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

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



&lt;/p&gt;

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

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



&lt;/p&gt;

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

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



&lt;/p&gt;

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

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



&lt;/p&gt;

&lt;p&gt;As Steve Krouse points out here, it is possible to get the real URL of the image in your doc (but confusingly, as soon as you click on the image to select it the URL becomes obfuscated!).&lt;/p&gt;

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

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



&lt;/p&gt;

&lt;p&gt;I also noticed the URLs in the source, and decided to make an easy way to access it. The one trick ended up being, because clicking on the image made it disappear, finding a way to tell the code which image you wanted!&lt;/p&gt;

&lt;p&gt;I looked through some JavaScript documentation and realized I could use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/mouseover_event" rel="noopener noreferrer"&gt;mouseover&lt;/a&gt; event to detect when someone was hovering over the image. Unfortunately this means that it won't work on a touchscreen device, but I'm guessing that most people who want to download an image are on a traditional computer.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it work?
&lt;/h2&gt;

&lt;p&gt;I needed a way to run my code on any Google Doc, there's probably a way to make Google Doc or Chrome extension to do this, but since I was asking people to run code in their potentially private docs I wanted to make the code as short and open source as possible.&lt;/p&gt;

&lt;p&gt;I decided that the best way to do this was a &lt;a href="https://support.mozilla.org/en-US/kb/bookmarklets-perform-common-web-page-tasks" rel="noopener noreferrer"&gt;bookmarklet&lt;/a&gt;. If you're unfamiliar with bookmarklets, they're bookmarks (usually placed in your bookmarks toolbar (Cmd-Shift-B to toggle this on and off on a Mac), that instead of navigating to a webpage, run JavaScript when you click them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Great! Tell me how to do it!
&lt;/h2&gt;

&lt;p&gt;To get started you'll have to "install" the bookmarklet. This is easy to do, and just means dragging a button into your bookmarks toolbar. &lt;a href="https://gdoc-image-dl.glitch.me/" rel="noopener noreferrer"&gt;I've hosted it on Glitch here&lt;/a&gt;. You can even drag it straight from one of the buttons on the embed below:&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/gdoc-image-dl?previewSize=100&amp;amp;path=index.html" alt="gdoc-image-dl on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;


&lt;p&gt;The instructions are simple!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Drag one of the bookmarklets below (see the embed above) to your bookmarks toolbar. The text displayed is what will be show on the toolbar:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then, when you're on a Google Doc, click the bookmarklet, then hover over an image embeded in the doc. Depending on your browser settings it will either download immediately, or open the actual image in a new tab.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Great my problems are solved forever!
&lt;/h2&gt;

&lt;p&gt;No guarantees that this will work long term, a quick look at the source code for any Google Doc will show that they're very complex! I wouldn't be surprised if Google changes the way these URLs work in the future, but this tool has worked for 6 months so maybe not!&lt;/p&gt;

&lt;p&gt;Long term I hope that they build in a way for people to download their images, but for now I hope this is helpful!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>web</category>
      <category>googledocs</category>
    </item>
    <item>
      <title>I Could Never Remember How to Make a Simple S3 Upload Feature So I Wrote It Down</title>
      <dc:creator>PatrickWeaver</dc:creator>
      <pubDate>Tue, 21 Apr 2020 12:53:56 +0000</pubDate>
      <link>https://dev.to/patrickweaver/i-could-never-remember-how-to-make-a-simple-s3-upload-feature-so-i-wrote-it-down-3n83</link>
      <guid>https://dev.to/patrickweaver/i-could-never-remember-how-to-make-a-simple-s3-upload-feature-so-i-wrote-it-down-3n83</guid>
      <description>&lt;p&gt;Whenever I start a new web project there is an ominous, literal, figurative, "cloud" lurking on the horizon: Will this project get complicated enough to need to be connected to S3 for file upload?&lt;/p&gt;

&lt;p&gt;More often than I'd like the answer is yes, and at this point I've re-learned how to connect a Node.js app to S3 more times than I'd like. Rather than keep learning just enough S3 to get a project working, and then instantly forgetting it, I decided to write the process down so I can follow my own instructions.&lt;/p&gt;

&lt;p&gt;I'm sure this will also find its way to people who know more than I do and might be able to alert me to anything I'm doing wrong. If this is you, &lt;a href="https://twitter.com/patrickweave_r" rel="noopener noreferrer"&gt;please reach out&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up AWS Authentication
&lt;/h2&gt;

&lt;p&gt;Connecting an app isn't usually the most difficult part of setting up S3. Where I always have to go back to documentation is setting up user and bucket permissions correctly. When I first started using S3 around 2013 a common recommendation was to just set buckets to public and link to objects directly. More recently though, many people (including Amazon), recommend not making buckets public.&lt;/p&gt;

&lt;p&gt;In my experience, it's best to create both a user and a policy when setting up AWS permissions. The keys you will use in your app will be associated with the user, and the permissions you want your user to have will be associated with the policy. This way, if your credentials are compromised you can create a new user, and all you have to do is add the policy to the new user.&lt;/p&gt;

&lt;p&gt;I've also found it's a best practice to create a new bucket for each of the small apps that I make. If you're working on a bigger project or want to set up a general purpose place to upload you may want to do this differently, but creating a unique bucket and user for each project helps me keep an eye on things, and not worry too much about credentials getting compromised. Because I only need one bucket for my app it's easier to create it in the AWS web interface than to build functionality to create buckets into my app.&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating a Bucket
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Log into AWS and click on "Services" in the top left. Select "S3" in the "Storage" section, then click on "Create Bucket" on the main S3 screen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwzb11or02x3fgsdlobl1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwzb11or02x3fgsdlobl1.png" alt="A screenshot of the main S3 screen" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give your bucket a name (this will be visible to users so something related to your app is best), and select a region (probably whichever is closest to your users), leave "Block all public access" checked, then click "Create bucket".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjqobhso6ba8idwecff7l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjqobhso6ba8idwecff7l.png" alt="A screenshot of the Create bucket screen" width="769" height="856"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Note your bucket name (probably in an ENV variable), it's now ready to receive uploads!&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Creating a Policy
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Click on your name in the top right. In the dropdown select "My Security Credentials", then in the "Identity and Access Management (IAM)" sidebar on the left, click on "Policies".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on the "Create policy" button. There are 2 ways to give your policy permissions, with the Visual Editor, and with JSON. We'll use the Visual Editor here, but you can probably just pate the JSON at the end with minor edits.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Visual Editor has 4 sections: Service, Actions, Resources, and Request Conditions. Start in Service and click on S3.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You want to add 3 specific actions: "PutObject" which allows uploading files, "GetObject" which allows reading files, and "DeleteObject" (I think you can figure this one out). "GetObject" is in the "Read" section, check the checkbox there. "PutObject" and "DeleteObject" are both in the "Write" section. At the end you should have 3 objects selected:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxq9fza7qlzaxb9s2uwxx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxq9fza7qlzaxb9s2uwxx.png" alt="A screenshot of the Create Policy actions selection" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the Resources section click on "Add ARN", then fill in your Bucket Name, and click on "Any" for Object name. This means that users with this policy can only perform the actions above on one bucket, but can perform those actions on any of the objects in that bucket.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn99mhoauau96yg2fp6j2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn99mhoauau96yg2fp6j2.png" alt="A screenshot of the Add ARN screen when creating a policy" width="800" height="636"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you click over to the JSON editor you should see the code below. You can also just copy this in. Note that you should edit the "Resource" property to have your actual bucket name:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VisualEditor0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:DeleteObject"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::YOUR_BUCKET_NAME/*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Click on "Review policy", then give your policy a name and a description. Then click "Create policy".&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Creating a User
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Click on Users in the left sidebar, then the "Add user" button at the top of the screen, give your user a name and select the checkbox for "Programmatic Access".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgi90azo1auvxzjdbamyz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgi90azo1auvxzjdbamyz.png" alt="A screenshot of the Add User screen" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;In the "Set permissions" section at the top of the page, click on "Attach existing policies directly". Search for the policy you just created, then select it and click "Next: Tags". You can skip Tags, and click "Next: Review", then click "Create user".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You will now save your user's credentials. This is the only time you will be able to do this, so make sure you save them somewhere safe. You will also need to add the credentials as ENV variables in your app. I recommend clicking the "Download .csv" button and saving the file, at least until you get your app set up.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3rqbznl2dlvif555eorn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3rqbznl2dlvif555eorn.png" alt="A screenshot of the attach policy section of the create user screen" width="800" height="629"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  A simple example app
&lt;/h2&gt;

&lt;p&gt;Congratulations! You are done with the AWS setup, now you can work on your app. I have a simple and heavily commented &lt;a href="https://aws-s3-example.glitch.me" rel="noopener noreferrer"&gt;example app&lt;/a&gt; I use to add this functionality to new projects:&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/aws-s3-example?path=index.html" alt="aws-s3-example on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;



&lt;p&gt;The app is a Node.js app using Express. It uses 3 additional packages. &lt;a href="https://www.npmjs.com/package/aws-sdk" rel="noopener noreferrer"&gt;aws-sdk&lt;/a&gt; adds functionality to communicate with S3, &lt;a href="https://www.npmjs.com/package/uuid" rel="noopener noreferrer"&gt;uuid&lt;/a&gt; is used for object names in S3, and &lt;a href="https://www.npmjs.com/package/multer" rel="noopener noreferrer"&gt;multer&lt;/a&gt; is used to process file upload to the server before passing it to S3.&lt;/p&gt;

&lt;p&gt;The index page is a plain HTML file, but there are two POST routes in server.js: &lt;code&gt;/upload-image-form&lt;/code&gt; and &lt;code&gt;/upload-image-async&lt;/code&gt;. The two routes are mostly the same, but are repeated for easy copying. &lt;/p&gt;

&lt;p&gt;Lines 1 through 24 of server.js are setting up the dependencies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;server.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The regular Node/Express stuff:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// I will use the UUID package for s3 file names&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;v4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uuidv4&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The AWS functionality is isolated for clarity:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./aws.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Multer processes the file in the request body&lt;/span&gt;
&lt;span class="c1"&gt;// This allows one file to be uploaded at a time.&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;multer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;multer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;memoryStorage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memoryStorage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;memoryUpload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;memoryStorage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;fileSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 4KB filesize limit&lt;/span&gt;
    &lt;span class="c1"&gt;//fileSize: 10*1024*1024, // 10 Mb filesize limit&lt;/span&gt;
        &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;single&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The uploading to S3 happens in the two POST routes, and in an isolated &lt;code&gt;aws&lt;/code&gt; module. I will go through the regular HTML form route here, but the JS API endpoint route is mostly the same.&lt;/p&gt;

&lt;p&gt;The route uses the previously defined &lt;code&gt;memoryUpload&lt;/code&gt; to capture a file object in req.body.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;server.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/upload-image-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;memoryUpload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we create an object to send to the &lt;code&gt;aws&lt;/code&gt; module (this is custom to this app, not the &lt;code&gt;aws-sdk&lt;/code&gt; npm package) with req.file. Most of the code below is comments, but the short version of what we need to send to the aws is an object with the properties &lt;code&gt;file&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt;. &lt;code&gt;file&lt;/code&gt; is the contents of the file, &lt;code&gt;id&lt;/code&gt; is what the file will be called in our AWS bucket:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;server.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cm"&gt;/*
    The file object has the following fields:

    fieldname: 'file' // This was specified in the file input field in the HTML
    originalname:     // The original name of the file
    encoding:         // The encoding of the file, don't worry about
                         this unless you want to look at the bytes.
    mimetype:         // This will tell you what the filetype is, even if there
                         is no extension, or if it's wrong.
    buffer:           // This is the actual data from the file
    size:             // Only some files will have this, the file's size in bytes
    */&lt;/span&gt;


    &lt;span class="c1"&gt;// This is optional, but a way to find the extension&lt;/span&gt;
    &lt;span class="c1"&gt;// of an image file.&lt;/span&gt;
    &lt;span class="c1"&gt;//const fileExt = file.mimetype.split("/");&lt;/span&gt;

    &lt;span class="c1"&gt;// These&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

      &lt;span class="cm"&gt;/* You may want to store this metadata in S3, but it's optional */&lt;/span&gt;
      &lt;span class="na"&gt;filetype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mimetype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

      &lt;span class="cm"&gt;/* You may want to add this to the filename */&lt;/span&gt;
      &lt;span class="c1"&gt;//fileExt: fileExt[fileExt.length - 1],&lt;/span&gt;

      &lt;span class="cm"&gt;/* You may want to use the original filename */&lt;/span&gt;
      &lt;span class="c1"&gt;//filename: file.originalname,&lt;/span&gt;

      &lt;span class="cm"&gt;/* We're going to use a random UUID file name in this example.
         One thing that this does is makes sure it is unique.
         If you upload a file with the same name it will overwrite the
         existing file! */&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Upload the file, see ./helpers/aws.js&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;aws.js&lt;/code&gt; module first there is some general configuration. This is where we will access our &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;, &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;, and &lt;code&gt;S3BUCKET&lt;/code&gt; ENV variables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;aws.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The AWS package is used for all AWS services,&lt;/span&gt;
&lt;span class="c1"&gt;// we only need the S3 part:&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;signatureVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Store your AWS creds in ENV variables:&lt;/span&gt;
&lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Your bucket isn't secret, but you may want to use&lt;/span&gt;
&lt;span class="c1"&gt;// different buckets for dev and production so it's&lt;/span&gt;
&lt;span class="c1"&gt;// helpful to store in an ENV variable.&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;bucketName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;S3BUCKET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are also 2 functions: &lt;code&gt;upload()&lt;/code&gt;, which takes one &lt;code&gt;uploadObject()&lt;/code&gt; parameter, uploads a file to S3, and returns confirmation and the S3 object's key, and &lt;code&gt;getSignedUrl&lt;/code&gt;, which takes an S3 key, and returns the file (more on this later).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;upload()&lt;/code&gt; is what we passed our &lt;code&gt;file&lt;/code&gt; object from &lt;code&gt;server.js&lt;/code&gt; to. This function is essentially a wrapper around the &lt;code&gt;aws-sdk&lt;/code&gt;'s &lt;code&gt;S3.putObject()&lt;/code&gt; method. We collect the necessary parameters in an object, then pass that object to the method which we've defined as &lt;code&gt;s3.putObject()&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;aws.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// AWS S3 Upload params:&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// S3 stores files in buckets, each bucket&lt;/span&gt;
      &lt;span class="c1"&gt;// has a globally unique name.&lt;/span&gt;
      &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucketName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

      &lt;span class="c1"&gt;// This will be the filename in AWS&lt;/span&gt;
      &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uploadObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

      &lt;span class="c1"&gt;// This is the contents of the file.&lt;/span&gt;
      &lt;span class="na"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uploadObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

      &lt;span class="c1"&gt;// This is optional, but your file in S3 won't have Content-Type&lt;/span&gt;
      &lt;span class="c1"&gt;// metadata unless you include it.&lt;/span&gt;
      &lt;span class="na"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uploadObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filetype&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;


    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;putObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is all wrapped in a &lt;code&gt;try&lt;/code&gt; / &lt;code&gt;catch&lt;/code&gt; block so if there aren't any errors we can pass the key back to &lt;code&gt;server.js&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;aws.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// Likely this won't happen because an error will be thrown,&lt;/span&gt;
    &lt;span class="c1"&gt;// but it's good to check just in case. ¯\_(ツ)_/¯ &lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;responseData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Upload failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// The response data has a single property, "ETag",&lt;/span&gt;
    &lt;span class="c1"&gt;// you probably won't need to do anything with it.&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3Data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

      &lt;span class="c1"&gt;// This key is what you would store in a DB, we didn't&lt;/span&gt;
      &lt;span class="c1"&gt;// get this back from S3, but since there wasn't an error&lt;/span&gt;
      &lt;span class="c1"&gt;// we trust that it is saved.&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;

      &lt;span class="c1"&gt;// Or, the url below could be stored if the permissions on the bucket&lt;/span&gt;
      &lt;span class="c1"&gt;// or the upload are publically viewable.&lt;/span&gt;
      &lt;span class="c1"&gt;//url: "https://" + bucketName + ".s3.amazonaws.com/" + params.Key&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Send the object with success and the key back to server.js&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s3Data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's important to note that the &lt;code&gt;id&lt;/code&gt; we pass back to &lt;code&gt;server.js&lt;/code&gt; isn't returned to us from the &lt;code&gt;s3.putObject()&lt;/code&gt; method. &lt;code&gt;s3()&lt;/code&gt; returns an &lt;code&gt;ETag&lt;/code&gt;, which isn't of much use for what we're doing, but it's enough to confirm that the upload completed successfully (What are ETags? &lt;a href="https://teppen.io/2018/06/23/aws_s3_etags/" rel="noopener noreferrer"&gt;teppen.io/2018/06/23/aws_s3_etags/&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Going back to server.js, this is where we would want to store our &lt;code&gt;id&lt;/code&gt; somewhere. This string is what we will need to retrieve the file from s3. In this app we're just demoing the upload functionality so we don't store it anywhere. We access it once though to show the user that it worked. This is where we will use the other function in the &lt;code&gt;aws&lt;/code&gt; module, &lt;code&gt;getSignedUrl&lt;/code&gt;. Because our S3 bucket permissions only let our AWS user access objects, and otherwise our bucket permissions are "No public access", we need to create a temporary signed URL to access the file.&lt;/p&gt;

&lt;p&gt;Using the id returned from the &lt;code&gt;upload()&lt;/code&gt; function we call the &lt;code&gt;getSignedUrl()&lt;/code&gt; function. When we get the signed url, we put it into some simple HTML to display it to the user (this is the main difference between the two &lt;code&gt;server.js&lt;/code&gt; routes):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;server.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// Confirm upload succeeded:&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Reponse Error: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/* - - - - -
      You might want to do something with the response.key or
      response.url here.
    - - - - - */&lt;/span&gt;


    &lt;span class="c1"&gt;// Because our bucket is not publically viewable we need to&lt;/span&gt;
    &lt;span class="c1"&gt;// get a signed URL to view the uploaded file. You DO NOT want&lt;/span&gt;
    &lt;span class="c1"&gt;// to store this signed URL in a DB, it will expire. You will&lt;/span&gt;
    &lt;span class="c1"&gt;// want to store either the key or url from the AWS response&lt;/span&gt;
    &lt;span class="c1"&gt;// above.&lt;/span&gt;

    &lt;span class="c1"&gt;// Get a new signed URL now that the file is uploaded:&lt;/span&gt;
    &lt;span class="c1"&gt;// Getting a signed URL requires the Bucket Name and the&lt;/span&gt;
    &lt;span class="c1"&gt;// file id, but we are using the same bucket name for everything&lt;/span&gt;
    &lt;span class="c1"&gt;// in this example. See ./helpers/aws.js for how this works.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSignedUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Very simple HTML response containing the URL and it rendered&lt;/span&gt;
    &lt;span class="c1"&gt;// as an image (if the file is not an image this will look like&lt;/span&gt;
    &lt;span class="c1"&gt;// a broken image).&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
      &amp;lt;p&amp;gt;
        &amp;lt;strong&amp;gt;Signed URL:&amp;lt;/strong&amp;gt; &amp;lt;a href="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/a&amp;gt;
      &amp;lt;/p&amp;gt;
      &amp;lt;h4&amp;gt;If it's an image:&amp;lt;/h4&amp;gt;
      &amp;lt;img src="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" width="400" /&amp;gt;
    `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;getSignedUrl()&lt;/code&gt; function in &lt;code&gt;aws&lt;/code&gt; is a wrapper around the &lt;code&gt;S3.getSignedUrl&lt;/code&gt; method (mostly putting it in our &lt;code&gt;aws&lt;/code&gt; module allows us to avoid passing the Bucket Name from our routes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;aws.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This function will get a signed URL which allows&lt;/span&gt;
&lt;span class="c1"&gt;// access to non public objects, and objects in non&lt;/span&gt;
&lt;span class="c1"&gt;// public buckets for a limited time.&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getSignedUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// We are already authenticated so we just need the&lt;/span&gt;
  &lt;span class="c1"&gt;// bucket name and the object's key.&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucketName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// The getSignedUrl method returns the url.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSignedUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getObject&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Try out the app (in this example uploads are limited in size to 4KB for safety). You can &lt;a href="https://glitch.com/edit/#!/aws-s3-example" rel="noopener noreferrer"&gt;remix the app on Glitch&lt;/a&gt; or &lt;a href="https://github.com/patrickweaver/aws-s3-example" rel="noopener noreferrer"&gt;fork it on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>javascript</category>
      <category>node</category>
      <category>s3</category>
    </item>
    <item>
      <title>Building a futuristic Record Player with Glitch and Raspberry Pi</title>
      <dc:creator>PatrickWeaver</dc:creator>
      <pubDate>Thu, 10 Jan 2019 01:12:51 +0000</pubDate>
      <link>https://dev.to/patrickweaver/building-a-futuristic-record-player-with-glitch-and-raspberry-pi-27a2</link>
      <guid>https://dev.to/patrickweaver/building-a-futuristic-record-player-with-glitch-and-raspberry-pi-27a2</guid>
      <description>&lt;p&gt;Early last year I wanted to explore the new &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/async_function" rel="noopener noreferrer"&gt;async/await&lt;/a&gt; functionality in Javascript so I started playing around with &lt;a href="https://async-await-machine.glitch.me/" rel="noopener noreferrer"&gt;a project&lt;/a&gt; on Glitch (&lt;a href="https://glitch.com/" rel="noopener noreferrer"&gt;glitch.com&lt;/a&gt;) that would call one API after another, then generate new API call options from each cycle. I compiled a long list of potential APIs to use, but didn’t get past chaining together an &lt;a href="https://dog.ceo/dog-api/" rel="noopener noreferrer"&gt;API that will respond with a picture of a specific breed of dog&lt;/a&gt;, and the &lt;a href="https://www.mediawiki.org/wiki/API:Main_page" rel="noopener noreferrer"&gt;Wikipedia API&lt;/a&gt; which could respond with the pages that came up in a search for the name of the breed.&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/async-await-machine?previewSize=100&amp;amp;path=index.html" alt="async-await-machine on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;


&lt;h5&gt;
  
  
  A Glitch embed of my Dog → Wikipedia API prototype: click on the name of a dog and you will get a picture of that dog, and a list of pages linked from that dog’s Wikipedia page.
&lt;/h5&gt;

&lt;p&gt;I started exploring what other APIs I could connect and realized that most of them seemed to enforce a relatively strict internal type system. Even from the many connections Wikipedia could provide it was hard to think of a potential next link that would always match up with the mishmash Wikipedia returned. While it might be easy to match up the Google Maps API to a weather API, connecting more abstract concepts was much more difficult.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ‘Record Player’ app
&lt;/h2&gt;

&lt;p&gt;While thinking through this an idea jumped out at me. I could connect the Google Vision API to Spotify to find albums based on pictures of a record cover. The idea seemed so obvious that I figured someone else had already done it (with more thorough research later I found a few similar projects but none that were fully implemented).&lt;/p&gt;

&lt;p&gt;I decided that this spark of an idea was already a lot more interesting than my infinite API art project, but it still had the Rube Goldberg-esque quality I was going for. Even so, I still might have abandoned it without a tool like Glitch, which to me says, “your wacky idea is worth making (and it won’t take very long).” Using the skeleton of my previous project, I was able to put together (in about an hour) an app that bounced an image off of the Google Vision API then brought up search results from Spotify. I sent it to a friend who works at Spotify, and when she confirmed that it didn’t already exist I decided to spend a few hours putting together a more polished version (the hardest part turned out to be drag and drop file upload).&lt;/p&gt;

&lt;p&gt;Thanks to Glitch’s embed feature you can take a look at the app (and the code!) below:&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/record-player?previewSize=100&amp;amp;path=index.html" alt="record-player on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;


&lt;h5&gt;
  
  
  A Glitch embed of the Record Player app
&lt;/h5&gt;

&lt;p&gt;The ‘final’ version of the project, “&lt;a href="http://record-player.glitch.me/" rel="noopener noreferrer"&gt;Record Player&lt;/a&gt;” was surprisingly simple. When given a reasonably well lit image of an even somewhat popular album the Google Vision API is able to identify the name of the album (occasionally just the artist). There were a few words I hard coded to ignore (things like “vinyl”, “cd”, or “import”), but other than that I was able to send Google Vision’s “best guess” to Spotify, and play the first result. I designed a goofy front end (with every music related emoji) and shared it on Twitter thinking that a few people would try it.&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/record-player?sidebarCollapsed=true&amp;amp;path=censoredWords.js" alt="record-player on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;


&lt;h5&gt;
  
  
  A Glitch embed with my surprisingly short list of censored words
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://twitter.com/anildash/" rel="noopener noreferrer"&gt;Anil Dash&lt;/a&gt; unexpectedly shared, and quickly it it started showing up &lt;a href="https://pitchfork.com/news/new-app-is-basically-shazam-for-album-covers/" rel="noopener noreferrer"&gt;in&lt;/a&gt; &lt;a href="https://www.pastemagazine.com/articles/2018/05/new-app-automatically-recognizes-album-covers.html" rel="noopener noreferrer"&gt;a&lt;/a&gt; &lt;a href="https://www.nme.com/news/music/app-album-cover-shazam-2306795" rel="noopener noreferrer"&gt;lot&lt;/a&gt; &lt;a href="https://www.engadget.com/2018/05/04/record-player-app-image-based-spotify-search/" rel="noopener noreferrer"&gt;of&lt;/a&gt; &lt;a href="https://www.altpress.com/news/app_matches_album_covers_spotify/" rel="noopener noreferrer"&gt;strange&lt;/a&gt; &lt;a href="https://www.androidauthority.com/record-player-spotify-google-861977/" rel="noopener noreferrer"&gt;corners&lt;/a&gt; &lt;a href="https://www.rollingstone.it/musica/news-musica/ora-esiste-uno-shazam-per-le-copertine-degli-album/410773/" rel="noopener noreferrer"&gt;of&lt;/a&gt; &lt;a href="https://lifehacker.com/stream-a-vinyl-album-by-snapping-a-pic-of-its-cover-art-1825800020" rel="noopener noreferrer"&gt;the&lt;/a&gt; &lt;a href="https://www.thecurrent.org/feature/2018/05/02/app-album-covers" rel="noopener noreferrer"&gt;internet&lt;/a&gt;. I was flattered by the coverage, but a few people were pointing out, “why would you take a picture instead of just typing in the name of the album?” First of all, it’s fun, which should be reason enough. But beyond that, there’s really no reason to right now, there are too many barriers and restrictions in place in the tools we use every day like Spotify and Google, that making a mashup like this will usually require an engineer instead of just an imagination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Record Player working on a Raspberry Pi
&lt;/h2&gt;

&lt;p&gt;From the beginning I wanted to create a physical version of Record Player (inspired by the amazing things they seem to be doing at &lt;a href="https://dynamicland.org/" rel="noopener noreferrer"&gt;Dynamicland&lt;/a&gt; without “traditional” input and output devices). It would be a machine that could detect when you put a record cover in front of it, and automatically start playing the first song. No screens, no searching, no curated playlist to distract from the physical thing in my hands.&lt;/p&gt;

&lt;p&gt;For the second time I was pleasantly surprised that by finding the right tools, a proof of concept was much easier than I expected. Using a Raspberry Pi with a camera module attached made a simple way to capture images. This left only the challenge of identifying when to start playing music. At first I thought that this might be easy to do by taking advantage of Google’s advanced image processing again, but I realized that the cost of the Google Vision API queries (you only get a certain amount free per month) would be prohibitive if I wanted the device to respond automatically.&lt;/p&gt;

&lt;p&gt;I decided to see if a simple algorithm running on the Raspberry Pi could identify when something new was placed in front of the camera, which seemed to work well enough. Connecting this to a slightly modified version of the Node.js server that runs the original Record Player Glitch app created exactly the machine I had imagined. The video below shows my “Record Player” automatically starting playback when it sees a record cover. This prototype has screens, but they’re only used to start the app and troubleshoot.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/288443309" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h5&gt;
  
  
  A video of the Record Player machine running on a Raspberry Pi
&lt;/h5&gt;

&lt;p&gt;Anil’s suggestion for using “Record Player” when he shared the Glitch app, was to see what song came up when you take a selfie, or “Shazam for your face” as he put it. Of course this was hilarious, and this is the kind of play and dynamic interactions that can happen when we start to break down the strict parameters and physical barriers that currently constrain our interactions with technology.&lt;/p&gt;

&lt;p&gt;Browsing Spotify is infinitely less interesting than browsing a friend’s record collection, but the convenience of any song ever recorded will usually win over the joy of discovering something unexpected. If we want this kind of ‘magic’ to bring technology like Google Vision out of prepackaged apps in our phones and laptops we need to allow for new ways of communicating with computers, and each other, that allow for experimentation, weirdness and imagination.&lt;/p&gt;

&lt;p&gt;When we start to join the interesting parts of the real world (for example: everything created before 1997) with the conveniences that smartphones have brought we can start to imagine dynamic digital experiences like the analog ones we have thought up for hundreds of years. Vinyl records themselves were designed as a static medium, but creative musicians saw a way to interact and started sampling and scratching them to create hip-hop.&lt;/p&gt;

&lt;p&gt;The idea for “Record Player” only seemed obvious to me after a few hours of sifting through documentation for dozens of APIs. As users we can’t see the “shape” of the internet, but these shapes determine what we can and can’t do, what we can and can’t imagine is possible. Surely every search on a weather or maps app is a city or address, almost all searches on Google Images are for nouns, and every search on Spotify or Genius is for an artist or song. But try connecting these services and APIs along unusual angles and you will start running into walls.&lt;/p&gt;

&lt;p&gt;Throw a song at Google Maps to see where it was recorded? Pass a Yelp page to the New York Times API to find any news about the neighborhood? These queries might be possible, but they almost always require diving into the nitty gritty of several APIs, and massaging the data to make the connection. In today’s world ideas like “Record Player” have to be a million dollar business to be worth doing. Many articles written about the Record Player app implied that Spotify had made it. The way that we experience modern technology tells us that only huge corporations are able to create new ways of experiencing the world.&lt;/p&gt;

&lt;p&gt;Products like &lt;a href="https://glitch.com/" rel="noopener noreferrer"&gt;Glitch&lt;/a&gt;, &lt;a href="https://www.raspberrypi.org/" rel="noopener noreferrer"&gt;Raspberry Pi&lt;/a&gt;, &lt;a href="https://ifttt.com/" rel="noopener noreferrer"&gt;IFTTT&lt;/a&gt;, &lt;a href="https://support.apple.com/guide/shortcuts/welcome/ios" rel="noopener noreferrer"&gt;Shortcuts&lt;/a&gt;, and the inspiration for the Raspberry Pi Record Player, &lt;a href="https://dynamicland.org/" rel="noopener noreferrer"&gt;Dynamicland&lt;/a&gt; make a more interesting and open future seem possible, now we just need to build the rest of it.&lt;/p&gt;

&lt;p&gt;Start now by remixing Record Player on Glitch:&lt;br&gt;
&lt;a href="https://glitch.com/~record-player" rel="noopener noreferrer"&gt;https://glitch.com/~record-player&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or make your own Raspberry Pi powered version:&lt;br&gt;
&lt;a href="https://github.com/patrickweaver/record-player-rpi" rel="noopener noreferrer"&gt;https://github.com/patrickweaver/record-player-rpi&lt;/a&gt;&lt;/p&gt;

</description>
      <category>raspberrypi</category>
      <category>javascript</category>
      <category>python</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
