<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Code Monkey</title>
        <link>https://paragraph.com/@codemonkey</link>
        <description>undefined</description>
        <lastBuildDate>Mon, 06 Apr 2026 02:32:57 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Angular and Rails API - presigned URLs for S3 image uploading]]></title>
            <link>https://paragraph.com/@codemonkey/angular-and-rails-api-presigned-urls-for-s3-image-uploading</link>
            <guid>NMWmcdVhVI31Sig4mgdg</guid>
            <pubDate>Sat, 27 Apr 2024 15:43:11 GMT</pubDate>
            <description><![CDATA[Prereq knowledge: A basic understanding of the following will help:S3 and how to setup a bucket&nbsp;etcRails APIsAngular (version 2&nbsp;upwards)Wha...]]></description>
            <content:encoded><![CDATA[<hr><p><em>Prereq knowledge: </em><br>A basic understanding of the following will help:</p><div class="relative header-and-anchor"><h4 id="h-s3-and-how-to-setup-a-bucket-etc">S3 and how to setup a bucket&nbsp;etc</h4></div><div class="relative header-and-anchor"><h4 id="h-rails-apis">Rails APIs</h4></div><div class="relative header-and-anchor"><h4 id="h-angular-version-2-upwards">Angular (version 2&nbsp;upwards)</h4></div><hr><div class="relative header-and-anchor"><h4 id="h-what-is-a-s3-presigned-url-anyway">What is a S3 presigned url&nbsp;anyway?</h4></div><p>Basically, using your Amazon security credentials, you <em>precreate a url</em> for a <em>specific file</em> that you expect to store in a <em>specific bucket</em>. Once you have created that url correctly, you can simply use it as the destination endpoint and attach the file in the body of your request. Ref <a target="_blank" rel="noopener" class="dont-break-out markup--anchor markup--p-anchor" href="http://docs.aws.amazon.com/mobile/sdkforios/developerguide/s3-pre-signed-urls.html">here</a>.</p><div class="relative header-and-anchor"><h4 id="h-introduction">Introduction</h4></div><p>There are various ways to upload files directly from the browser to S3. You need to ‘sign’ what you are sending using your Amazon id key and secret key — but obviously you don’t want to expose them in client side javascript.</p><p>If you search you can find plenty of examples of simply requesting your credentials from your server (over https of course) and then using them in the client to sign the request client side, and then upload the file. <a target="_blank" rel="noopener" class="dont-break-out markup--anchor markup--p-anchor" href="https://github.com/ntheg0d/angular2_aws_s3/blob/master/app/upload/upload.component.ts">This</a> is one such example. 81 lines of code! It also seems quite complicated too!</p><blockquote><p>Read on for a MUCH easier solution!</p></blockquote><img src="https://storage.googleapis.com/papyrus_images/00b076fedaddbe1a867138703d0beab7.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAbCAIAAACSpRrNAAAACXBIWXMAAAsTAAALEwEAmpwYAAAErElEQVR4nK2VfUwTdxjHb/tzyZSoQUHIEiDhJf6xbH8RmZsbbOgSQDDM7I/RsQ1pAaXtMkYD8tIMhxTZRksplV6h0tI3Sk9Ar00JND1A0APmy12po+VXyGBX+GPmQJOZ37J2E/ElStk3v1zud5c8n/s+zz2/B2lrkwu/qzjf2Ph9pUir02l1unK+gCDGvL4Fr29heWVleWVl3uu9c5d60cJt9k8yj4dWVvaJjI+PSWXyThVaU1en1xsQ3GaXyxV5Obn5J/M/ysgo4HBSU1Nr6urQSyq5vKNcUFFT19CpUod4z11TN8mys+V8vrCYy6sUVX1TVGyxYrjNbum3EsQYwjDMn/fvR7+x51BCIvKf9r4ZkZ56BEFeD21P5ObDsMSyLLK2tsYwjAO3OXCbLKgerdaB2yYnJjVaXZtcaTD04Tb7SwM9qS0Alr3v83ntdsf1iSmadj/5eudigwAWAPDzT61RB2JSUg6p1d28knKLFXv8OetBhQ9gGGZ5ZYVl2T17I6MPxsrlHQJhZfPF1v/NAcMwPp/34cZDXqngw/RPd+0+EBefHBeffCL3lMHQFwisAuCfnp6laTdBjIeuBDE+dZOkafdLnW0CIIQA+CP3v7UvMhZ5LWJfZGxS8tsxsQnvvJsaF598OC0dVV8uLCxuaGgq/Ipb8GURr6S8pUUaCKy+EsAz54EQtrRIhd+KAPC73Z7p6VkA/DTtnp6eJYjxsCv/LwAAsLa2VlZaxuPxLBaLSFQpEAh0Ol2LTCFuaimvqDL2WSGE61v116NHrwogSZIgxkiSHHWOUhSFYZjL5SJJkvLcu03PDbsIr29hRw4oimr/RerAbfaBa2qVRqlEZVKF2dRfLaz4oapGrdLI5R3hZekfwKLfT1GUa9QZOhUS4pI+OJLe1NgMAHCNOm//essz56Fp944cAABYls3MPJaW9n5tTf09z28T49fDi/hcwB8URUEIhx0Ol8s16hy1Wi12u33e632wsQF3ps0aQAjrq8+dOc3ll5Z9fjK/vvpcV1tr+4Xzg2Z9p1J19oywqbH5fMOPAIBtAxb9fpIkIYRdarWiXdHd1d2j0QwNDioVii4U/X1paWlxCQAQCKwyDBOmAwDAg42NzMzMqKjoxMSkMm7xGc4XX3MKnE5nGEGfBgAApm7cgBDeuUsdPZ6FIEjErt0piUkxBw8iCBIZuT8vN6+tTR4+gGGYea83dGivr6+HShIIrIaeMME23G7qn3YQKrLBYFCpOnu0WgzDUBRtlUoxDOvt7cWCAsAfvoPQYadSdTZeaBQHJRAIautqJRIJl8vlcDg2G/5sJz8eRI/n0gsdzM7MQAhzcnKys7NKS0okEolIJBKL68VisUQiKTpdxOfzURTt0Wqv4bjRbLqKX33FGbf5F0EI8z87dfi9owKBsLn5YjAzV8ym/mHHMIYNEMSYyWjW641D1j7HADY5MekYGTGb+jUa3eDAkF5vlMlkGHblWZebfcCyLE27p2Zu9fb2isViFEVNRrPZ1N/fh3V19TgcI6HtFaMet5gnJyYtA0Maja5DcUmpRFFULRKJjAbjs/NnSyeTQVEU5fN5adodut9mUZ+Tor8BAnBh9LFIoc4AAAAASUVORK5CYII=" nextheight="854" nextwidth="998" class="image-node embed"><div class="relative header-and-anchor"><h4 id="h-a-brief-explanation-of-the-code-in-the-2-pics">A brief explanation of the code in the 2&nbsp;pics</h4></div><blockquote><p>you can skip this bit if you want, I won’t mind — it’s not my code!&nbsp;;)</p></blockquote><p>The template as the start is just a standard file input to select a file and a submit button that will call the Upload method.</p><p>In the constructor we inject the uploadService (which is poorly named, because its not an upload service but actually a service that simply returns your S3 sig and policy — which are stored in the local variables declared at lines 19 and 20).</p><p>When the component initializes, on line 17 the call is made to the server to get the policy from your server. The response is handled on line 27 to populate the local variables.</p><blockquote><p>Now comes the icky part:</p></blockquote><img src="https://storage.googleapis.com/papyrus_images/3d3a0e5fcf96378110a54cf5dbe5c322.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAbCAIAAACSpRrNAAAACXBIWXMAAAsTAAALEwEAmpwYAAAErElEQVR4nKWVbUxTVxjHb7Jvy4huMt/Gi4AIBQQKYhjv74qoC0NAsyllbnetSGlhUEvgAgXaCxsVtKII2iLInYVyQ4VKJX4A3SwtED0BAilmGW/JJDeS0FwgobmLXCwb2ZJS/jm5uR/Oc355nuf/nAORJAkAIMllHMcjIiPhdclkMhxXoRLJ9MwMtQORJAnRgCWTSa/Xx4RHQhBkv9v+wrk0Doer7ul9u7BAkuROAROTEwRBUBSF3W/es+vTC2npfj7+DN9jPoHHQ2JPiiTVAICXOr2m71mHCqd3bg8wZDDQYY9aWiPCwosKi8tFaC6fH3j8y5CE0+cvZV7MhM8kpzbIFd0arS0AAAAdNqI3oBIJDLMfND8oKy3jcnNgmN3X17dmNhMEsWZes7FEdAYkSUrFVSmnzzK8vA45O1ehVXq9/rH6sdFo3O65WwF6vZ4giNnZWXJDy+Q/RK3vs3y3xFsFAAC87zDW5ubmlnU1C8fxJ096Wh+24jjerFDI5XJsXfh7deJ4Z2NTI4Zh9fX1PZoeawFLJhMAICo0nOHJ8PVnFheVnoxPqkGlaEl5xsXL0VEJ9+7L5+bmt2tZC+D1ZpMrKstF4vy8a+mp396S3VY0Nkkqq7jZuZwfsxvvNm3LQv9hUxzH837Kk8lkv9RICwoKtNqn8/N/LZlM4xOTAADd0LCNAItNeTk8CII+tj9o94XLEQ+/xFPJ+w+6cXPyxNVSsVgsklRv9+bYdNHs7CxFUWw2G4Igd+8AB4b/Ud8gF1evz+wdkpKSXVwZn9jty2D9MDDw244Gra621snJKSw6/sy51OjohMBjIe5HvKXXZfW3m04kfpWWnsHl5Q8MDLx6DYaHRug1PjY+PDRiMAyPjo7pBg26QcNLnX50dIy2A0EQGxm8XVigKKq3V5PJypTekFVfv4kgIhSt4fLyFYoWpVJ1icUWCIp5fEG3RitvwWpQKVJUhhSV3ai7iaI1JSWVdXW3+HwBny+4coVfWyujS7IBAADQgIY7dw45OyenfZPGgr+HsyIiTpxKSsnNF36dct6TwQxgBoeGxXA42c3NbVNTb2zpAYvFgiBor6PbHlePvfudIQiy230gNvHs5e/ggvzCivKqxrtNHA43MCi8q0u9Hr+8ZjZ/GP5ly8/qyioNWHy3+K8eXBMIaICTF/Oob5Ang+npG+Tt7ReXkPCwDcPxTq32qS1N7u/vpzOgAe7eAR5BYcEhkSHhcUGhUbEx8S2/PlJ2tKMSSbNCYQvAaDTSYYODulZlh6zhHoa1q9UaDGsXVVQjJeVCYWlPt1auaFPhaqVS1a3RdnSpV1dWrAVYmpzL5/sEhzke9oCgjxITk5RKXCgsLSuTCIUIhrXTflUqVb3aZ+qeXmueh80MlkxLdJUoilozm60vgrUuIkkSwzBmQAA/++rP4goEQbbUmtzwyfIH5yxvA2A0Guk7NS46juF+mAfDiKiyAkXZbDaClCAI8uL5C2uO+18AAGBubp6iqMLSyl37HCKi4j4/4AhBEAyzAQCWKbEdMGQwTM/MTE29+ePP6ZFX4PnvOjA+OTE5Qb90O9HmVUGPAkmaLGvJZFp8t0jsWH8D+qomJeXiv2UAAAAASUVORK5CYII=" nextheight="840" nextwidth="1000" class="image-node embed"><p>Line 41 builds a timestamp (as S3 requires that requests are stamped) and then line 53 is the upload method which will build the form with all the required data for S3 (ugh!) and finally attach the file at line 69.</p><p>If you wanted to you could also attach the file as a Blob64 encoded string, but then you’d need even more code to convert the file first. <a target="_blank" rel="noopener" class="dont-break-out markup--anchor markup--p-anchor" href="https://gist.github.com/rmcsharry/9ecf2b4741556c1db9eff315a971987a">Kinda like this.</a></p><div class="relative header-and-anchor"><h4 id="h-to-put-or-to-post">To PUT or to&nbsp;POST?</h4></div><p>The above example uses a standard multi-part form post to amazon at line 71 — but notice that is NOT a presigned URL It is simply creating a policy and signature, then <strong>POST</strong>ing the form data to the bucket. IMHO an easier way is to just use <strong>presigned urls.</strong> NOTE these MUST use a PUT — so you cannot post a form like above!</p><p>Since you have to call your server anyway to access your (secure) S3 credentials, I figured why not just take the easier <strong>route?</strong> (excuse the pun)</p><div class="relative header-and-anchor"><h3 id="h-the-route-of-least-resistance">The route of least resistance</h3></div><p>/products/product-form.component.ts</p><pre data-type="codeBlock" language="typescript"><code>...
<span class="hljs-title function_">uploadToS3</span>(<span class="hljs-params">presignedUrl: <span class="hljs-built_in">string</span></span>){
  <span class="hljs-variable language_">this</span>.<span class="hljs-property">_productService</span>.<span class="hljs-title function_">putFileToS3</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">selectedFile</span>, presignedUrl).<span class="hljs-title function_">subscribe</span>(
  <span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(response));
}</code></pre><p>When <strong>uploadToS3</strong> is called, it subscribes to a method in my ProductService (where all my methods for talking to the Product endpoints live, as per a normal Angular service setup). Note here that I am just logging the response for simplicity — but actually S3 will return a 200 with no content. So you only need to handle any error returned (which you could do in the <strong>putFileToS3</strong> method below).</p><p>And here is that method in my ProductService class:</p><p>services/product.service.ts</p><pre data-type="codeBlock" language="typescript"><code>...
<span class="hljs-meta">@Injectable</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ProductService</span> {
  <span class="hljs-title function_">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> http: Http
  </span>) {}

...

   <span class="hljs-title function_">putFileToS3</span>(<span class="hljs-params">body: File, presignedUrl: <span class="hljs-built_in">string</span></span>){
   <span class="hljs-keyword">const</span> headers = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Headers</span>({<span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'image/jpeg'</span>});
   <span class="hljs-keyword">const</span> options = <span class="hljs-keyword">new</span> <span class="hljs-title class_">RequestOptions</span>({ <span class="hljs-attr">headers</span>: headers });

   <span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">http</span>.<span class="hljs-title function_">put</span>(presignedUrl, body, options).<span class="hljs-title function_">map</span>(
       <span class="hljs-function">(<span class="hljs-params">response: Response</span>) =&gt;</span> response.<span class="hljs-title function_">json</span>()
   )
}</code></pre><p>Note that I have a LOT less code than the first example.</p><div class="relative header-and-anchor"><h3 id="h-is-this-some-kind-of-angular-magic">Is this some kind of Angular&nbsp;magic?</h3></div><p>Not at all. Notice that my <strong>uploadToS3</strong> is expecting a <strong>presignedUrl…</strong><em>so where is that coming from then?</em></p><p>When the user selects a file and clicks the upload button, I expect them to also have filled out all other product attributes and save the product to my server. So my form has a file input plus all the other inputs needed. Upon clicking submit, the following method fires and posts ALL the product data (but NOT the file or even the filename or type — but you could if you wanted to). Everything about the file remains on the client.</p><p>products/my-product.component.ts</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-title function_">createOnApi</span>(<span class="hljs-params"></span>) {
  <span class="hljs-variable language_">this</span>.<span class="hljs-property">_productService</span>.<span class="hljs-title function_">postProduct</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">myForm</span>.<span class="hljs-property">value</span>).<span class="hljs-title function_">subscribe</span>(
    <span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
      <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">successToast</span>(<span class="hljs-string">'Product was created'</span>);
      <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">uploadToS3</span>(data.<span class="hljs-property">presigned_url</span>);
      <span class="hljs-variable language_">this</span>.<span class="hljs-property">_router</span>.<span class="hljs-title function_">navigate</span>([<span class="hljs-string">`/pages/products/<span class="hljs-subst">${data.id}</span>`</span>]);
   },
   <span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> {
      <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">errorToast</span>(<span class="hljs-string">'Product was not created'</span>);
   }
  );
}</code></pre><p>My API saves the Product and in the response we get back the <strong>presigned_url</strong> as an attribute on the Product class, which we then pass along to the <strong>uploadToS3</strong> to transfer the actual file to S3. Here is the Product class:</p><p>models/product.ts</p><pre data-type="codeBlock" language="typescript"><code><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Product</span>{
  <span class="hljs-title function_">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">public</span> id: <span class="hljs-built_in">number</span>,
   ...
  </span>) {}
  <span class="hljs-keyword">public</span> <span class="hljs-attr">presigned_url</span>: <span class="hljs-built_in">string</span>
}</code></pre><p>Meanwhile we navigate to the page for the newly created product. By the time that page loads, S3 will already have the file (unless you are loading stupidly large images of course!).</p><div class="relative header-and-anchor"><h3 id="h-is-this-some-kind-of-rails-magic">Is this some kind of Rails&nbsp;Magic?</h3></div><p>Not at all. The Product class in Rails includes a concern which takes care of creating the <strong>presigned_url</strong> as per the S3 docs I linked at the start.</p><p>models/product.rb</p><pre data-type="codeBlock" language="ruby"><code><span class="hljs-keyword">class</span> <span class="hljs-title class_">Product</span> &lt; <span class="hljs-title class_ inherited__">ApplicationRecord</span>
  <span class="hljs-keyword">include</span> <span class="hljs-title class_">Amazon</span>
  <span class="hljs-built_in">attr_accessor</span> <span class="hljs-symbol">:presigned_url</span>
<span class="hljs-keyword">end</span></code></pre><p>Notice <strong>presigned_url</strong> is not stored as part of the model. I use it once to upload the file from the client (and anyway by default they expire after 15 minutes). See below for what IS persisted in the database for the image.</p><p>Finally, here is the magic that creates this magic S3 <strong>presigned url</strong>:</p><p>models/concerns/amazon.rb</p><pre data-type="codeBlock" language="ruby"><code><span class="hljs-keyword">require</span> <span class="hljs-string">'aws-sdk'</span>
<span class="hljs-keyword">module</span> <span class="hljs-title class_">Amazon</span>
<span class="hljs-keyword">extend</span> <span class="hljs-title class_">ActiveSupport::Concern</span>

included <span class="hljs-keyword">do</span> 
  after_save <span class="hljs-symbol">:create_presigned_url</span>
<span class="hljs-keyword">end</span>

<span class="hljs-keyword">def</span> <span class="hljs-title function_">create_presigned_url</span>
  filename = <span class="hljs-string">"<span class="hljs-subst">#{<span class="hljs-variable language_">self</span>.id}</span>.jpg"</span>
  <span class="hljs-title class_">Aws</span>.config[<span class="hljs-symbol">:credentials</span>]=<span class="hljs-title class_">Aws::Credentials</span>.new(
  <span class="hljs-title class_">Rails</span>.application.secrets.aws_access_key_id,
  <span class="hljs-title class_">Rails</span>.application.secrets.aws_secret_access_key)
  s3 = <span class="hljs-title class_">Aws::S3::Resource</span>.new(<span class="hljs-symbol">region:</span><span class="hljs-string">'eu-west-2'</span>)
  bucket = <span class="hljs-title class_">Rails</span>.application.secrets.s3_bucket_name.to_s
  obj = s3.bucket(bucket).object(filename)
  <span class="hljs-variable language_">self</span>.presigned_url = obj.presigned_url(<span class="hljs-symbol">:put</span>, { <span class="hljs-symbol">acl:</span><span class="hljs-string">'public-read'</span> }) <span class="hljs-comment">#, expires: 10*60)</span>
  <span class="hljs-variable language_">self</span>.update_column(<span class="hljs-symbol">:image_url</span>, obj.public_url)
<span class="hljs-keyword">end</span></code></pre><p>Note the very last line — this stores the <strong>public_url</strong> of the image in the database as <strong>image_url</strong>. When you create a file in an S3 bucket, it will automatically provide this public_url. Since I set <strong>acl: ‘public-read’ </strong>that allows anyone to see the image.</p><p><em>Obviously this example is simple and only allows for one image per product, but it shows the general idea of how you can get S3, Rails and Angular to play happily together. Without getting bruised hopefully!</em></p><p></p>]]></content:encoded>
            <author>codemonkey@newsletter.paragraph.com (Code Monkey)</author>
            <category>angular</category>
            <category>ruby on rails</category>
        </item>
    </channel>
</rss>