<?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: Gabriel Chertok</title>
    <description>The latest articles on DEV Community by Gabriel Chertok (@iamcherta).</description>
    <link>https://dev.to/iamcherta</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%2F51220%2Fe85f52c0-7de2-4c35-8a85-b11902932737.jpg</url>
      <title>DEV Community: Gabriel Chertok</title>
      <link>https://dev.to/iamcherta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iamcherta"/>
    <language>en</language>
    <item>
      <title>Hotwire empty states with Alpine.js</title>
      <dc:creator>Gabriel Chertok</dc:creator>
      <pubDate>Mon, 07 Feb 2022 21:49:38 +0000</pubDate>
      <link>https://dev.to/iamcherta/hotwire-empty-states-with-alpinejs-4gpo</link>
      <guid>https://dev.to/iamcherta/hotwire-empty-states-with-alpinejs-4gpo</guid>
      <description>&lt;p&gt;As I work more with Hotwire, replacing small chunks of the UI instead of re/rendering the whole page, I start to lose some of the benefits I used to have. Empty states are one of such things you don't get if you change refresh actions for more discrete ones like append or remove.&lt;/p&gt;

&lt;p&gt;Take a look at the following example. I have a list of items I want to display, and I want to show an empty state when there're no items.&lt;/p&gt;

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




&lt;p&gt;This behavior is easy to achieve with regular Rails, but adding Turbo Frame features like removing or adding to the main frame loses that ability.&lt;/p&gt;

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

&lt;p&gt;The initial render works fine because the whole page gets evaluated, but as Turbo takes control and submits the form we never get our empty state back. Here's the backend code that sends the append or delete messages back to Turbo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NodesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="vi"&gt;@node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="n"&gt;node_params&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
      &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;turbo_stream&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;turbo_stream: &lt;/span&gt;&lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;nodes_url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :unprocessable_entity&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy&lt;/span&gt;
    &lt;span class="vi"&gt;@node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;
      &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;turbo_stream&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;turbo_stream: &lt;/span&gt;&lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;nodes_url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :unprocessable_entity&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Update all-the-frames
&lt;/h2&gt;

&lt;p&gt;This problem can be easy to fix, or at least to hack. We could tell the backend to replace the wrapping turbo frame instead of removing/adding stuff to it. Telling Turbo to do this is easy, but it feels a bit out of place, as it starts resembling a lot to Turbolinks, and we don't want that. We want to be very prescriptive about our updates. Here's how you can achieve that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;turbo_stream&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;turbo_stream:
        &lt;/span&gt;&lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"nodes/_collection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;locals: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;nodes: &lt;/span&gt;&lt;span class="no"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;   
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;turbo_stream&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;turbo_stream:
        &lt;/span&gt;&lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"nodes/_collection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;locals: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;nodes: &lt;/span&gt;&lt;span class="no"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;   
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  CSS solution
&lt;/h2&gt;

&lt;p&gt;Another possible way of achieving the same behavior is with CSS. We can piggyback on the &lt;code&gt;:empty&lt;/code&gt; pseudo-class and show a blank state using the content property. That's ok, but again a little hacky for my taste. Having a CSS rule probably won't cut it for screen readers, and it's also not very semantic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#nodes&lt;/span&gt;&lt;span class="nd"&gt;:empty:after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"Add new nodes 👇"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#nodes&lt;/span&gt;&lt;span class="nd"&gt;:empty&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid grid-flow-row gap-4 w-full"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Keeping the if/unless because rendering an empty array yields a blank space and breaks the :empty css selector --!&amp;gt;
  &lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"nodes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"grid grid-flow-row gap-4 w-full"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;
    &lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;
      &lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;
    &lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;
  &lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;
&amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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




&lt;h2&gt;
  
  
  JS solution
&lt;/h2&gt;

&lt;p&gt;So far, we haven't tried to build a solution with JS, and I think we need to roll up our sleeves and make one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A little disclaimer: I'm not a fan of Stimulus. After years of writing reactive UIs with React, using the DOM imperative API feels odd. Most of the Stimulus code I see out there leaves the DOM manipulations up to you to write on instance methods. My experience is that manually updating the DOM on state changes is error-prone; that's why most UI frameworks choose some flavor of reactive programming.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There must be a way to write the last 5% of the features that Hotwire can't handle reactively, and it turns out that there is. Say hi to &lt;a href="https://alpinejs.dev" rel="noopener noreferrer"&gt;Alpine.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Alpine.js feels a lot like angularjs, but without the mess and probably with more modest ambitions. It uses directives to enhance your HTML.&lt;/p&gt;

&lt;p&gt;The syntax is straightforward, and I don't think I can do better than &lt;a href="https://alpinejs.dev/start-here" rel="noopener noreferrer"&gt;Alpine.js docs&lt;/a&gt; explaining it. I'll throw here some snippets assuming the syntax is so easy that you'll understand it without needing to know Alpine.js.&lt;/p&gt;

&lt;p&gt;Back to our empty state problem, here's what I want. A piece of data holding whether I need to show the blank state or not and a directive that shows/hides it based on such value.&lt;/p&gt;

&lt;p&gt;Now, we only need to know when to recompute this variable. Maybe I can use some Turbo callbacks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid grid-flow-row gap-4 w-full"&lt;/span&gt;
  &lt;span class="na"&gt;x-on:turbo:submit-end=&lt;/span&gt;&lt;span class="s"&gt;"zeroState = $el.querySelectorAll('#nodes turbo-frame').length === 0"&lt;/span&gt;
  &lt;span class="na"&gt;x-data=&lt;/span&gt;&lt;span class="s"&gt;"{
    zeroState: &lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'false'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;
  }"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"nodes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"grid grid-flow-row gap-4 w-full"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;x-show=&lt;/span&gt;&lt;span class="s"&gt;"zeroState"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add new nodes 👇&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;I thought hooking into Turbo events would allow me to recompute the property based on the DOM contents, but I was wrong. Turbo fires a &lt;code&gt;turbo:submit-end&lt;/code&gt; when the form submission ends, but querying the DOM at this point will compute the wrong thing since DOM hasn't been updated yet. &lt;/p&gt;

&lt;p&gt;Luckily, we have a standard web API that tells you when a piece of the DOM changes, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver" rel="noopener noreferrer"&gt;Mutation Observer&lt;/a&gt;. We can use Mutation Observer to call us back when the DOM mutation has been applied, and the &lt;code&gt;zeroState&lt;/code&gt; variable can be recomputed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid grid-flow-row gap-4 w-full"&lt;/span&gt;
  &lt;span class="na"&gt;x-data=&lt;/span&gt;&lt;span class="s"&gt;"{
    observer: undefined,
    zeroState: &lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'false'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;
  }"&lt;/span&gt;
  &lt;span class="na"&gt;x-init=&lt;/span&gt;&lt;span class="s"&gt;"
    observer = new MutationObserver(() =&amp;gt; {
      zeroState = $el.querySelectorAll('#nodes turbo-frame').length === 0
    })
    observer.observe($el, {
      childList: true,
      attributes: false,
      subtree: true,
    })
  "&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"nodes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"grid grid-flow-row gap-4 w-full"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;x-show=&lt;/span&gt;&lt;span class="s"&gt;"zeroState"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add new nodes 👇&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



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

&lt;p&gt;Perfect! This code nicely does the trick, but wait, it's not very reusable, and empty states will appear everywhere in my app. We could copy and paste this boilerplate around, but we can do a little better by creating a custom directive that avoids us writing all that horrible x-init directive every time.&lt;/p&gt;

&lt;p&gt;Another problem with this code is that I haven't found a way to execute cleanup code when the HTML containing the directive goes away. This cleanup is important because otherwise, we will be adding multiple observers and never disconnecting them. Here's what we want our code to look like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid grid-flow-row gap-4 w-full"&lt;/span&gt;
  &lt;span class="na"&gt;x-mutation-observer.child-list.subtree=&lt;/span&gt;&lt;span class="s"&gt;"zeroState = $el.querySelectorAll('#nodes turbo-frame').length === 0"&lt;/span&gt;
  &lt;span class="na"&gt;x-data=&lt;/span&gt;&lt;span class="s"&gt;"{
    zeroState: &lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'false'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;
  }"&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"nodes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"grid grid-flow-row gap-4 w-full"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;x-show=&lt;/span&gt;&lt;span class="s"&gt;"zeroState"&lt;/span&gt; &lt;span class="na"&gt;x-transition:enter.duration.500ms&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add new nodes 👇&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating directives in Alpine.js is an advanced topic, more so if you want to mess with the reactive part, we only want to hide some code behind the directive and execute some cleanup tasks when it is unmounted from the DOM, so ours should be easy to build. Here's all the code.&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="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hotwired/turbo-rails&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alpinejs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alpine:init&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Alpine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;directive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mutation-observer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;modifiers&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;evaluateLater&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cleanup&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluateLater&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MutationObserver&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;childList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;modifiers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;child-list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;modifiers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;attributes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;subtree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;modifiers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;subtree&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;})&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;In our directive, we want to evaluate the expression -the thing that gets passed to the HTML attribute- every time MutationObserver calls us back. For that reason, Alpine.js has an &lt;code&gt;evaluateLater&lt;/code&gt; function that returns a function that evaluates the expression when called.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;modifiers&lt;/code&gt; are what gets sent to the directive after the dot. In our case, those are the params for the MutationObserver config object; this way, we can have a pleasant and expressive API.&lt;/p&gt;

&lt;p&gt;Finally, a directive does have a way to clean up via the &lt;code&gt;cleanup&lt;/code&gt; callback. We can use that to disconnect the observer instance.&lt;/p&gt;

&lt;p&gt;Aaaand that's it, there's no more work to do. After registering the directive, it will be available as a built-in one. I added some transitions using Alpine.js &lt;code&gt;x-transition&lt;/code&gt; directive to make add some animation for the empty state, and here's how it looks like.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  CSS solution v2
&lt;/h2&gt;

&lt;p&gt;Before wrapping up, I want to mention a better way of doing this that was pointed out by &lt;a href="https://twitter.com/faqndoE97" rel="noopener noreferrer"&gt;Facundo Espinosa&lt;/a&gt;, who was kind enough to proofread this post and give me &lt;a href="https://twitter.com/_swanson/status/1456653563011211268?s=20&amp;amp;t=H_EyO_ZUwt7giCmXI9paZA" rel="noopener noreferrer"&gt;this alternative&lt;/a&gt; which I think is better. Yes, better than the wall of code I just made you read; sorry for that 🙏.&lt;/p&gt;

&lt;p&gt;CSS has the &lt;code&gt;:only-child&lt;/code&gt; pseudo-class that gets applied when the element is the only child. With that in mind, we could just write something similar to this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid grid-flow-row gap-4 w-full"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"nodes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"grid grid-flow-row gap-4 w-full"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hidden only:block text-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add new nodes 👇&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Still, learning Alpine.js and how to create a custom directive was fun, and I'm sure it will help me in the future.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>hotwire</category>
      <category>alpinejs</category>
    </item>
    <item>
      <title>🛡 Blitz Guard</title>
      <dc:creator>Gabriel Chertok</dc:creator>
      <pubDate>Wed, 10 Feb 2021 13:33:09 +0000</pubDate>
      <link>https://dev.to/iamcherta/blitz-guard-936</link>
      <guid>https://dev.to/iamcherta/blitz-guard-936</guid>
      <description>&lt;p&gt;This article was originally posted on &lt;a href="https://monolith-bias.com/posts/blitz-guard"&gt;Monolith Bias_&lt;/a&gt; the technical blog of the &lt;a href="https://www.ingenious.agency"&gt;Ingenious&lt;/a&gt; development team.&lt;/p&gt;




&lt;p&gt;As great as Blitz is for developing full-stack applications, I still miss the batteries included feeling Ruby on Rails has. I know I can't compare a mature framework like Rails to Blitz that barely has a year old, but I certainly miss the "there must be a gem for that" feeling.&lt;/p&gt;

&lt;p&gt;One of the things I miss the most when working on a web app is a centralized place to manage authorization. Authorization is the most common requirement a web app may have. While Blitz itself tries to address this issue with the &lt;code&gt;$authorize&lt;/code&gt; method that's built-in on the session, it falls short when the authorization is dependent on data attributes; technically called &lt;a href="https://en.wikipedia.org/wiki/Attribute-based_access_control"&gt;attributes-based access control&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The good news is that &lt;a href="https://twitter.com/ntgussoni"&gt;Nico Torres&lt;/a&gt;, a friend and former Ingenious employee, created &lt;a href="https://ntgussoni.github.io/blitz-guard/docs"&gt;Blitz Guard&lt;/a&gt;, the API is close to &lt;a href="https://github.com/CanCanCommunity/cancancan"&gt;RoR cancancan gem&lt;/a&gt; but adapted to work with Blitz.&lt;/p&gt;

&lt;h1&gt;
  
  
  Installation
&lt;/h1&gt;

&lt;p&gt;With Blitz Guard latest release, you can execute the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;blitz &lt;span class="nb"&gt;install &lt;/span&gt;ntgussoni/blitz-guard-recipe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line uses the Blitz Guard recipe to install the library. &lt;a href="https://blitzjs.com/docs/using-recipes"&gt;Recipes&lt;/a&gt; are an excellent, guided way to install new software in your app. It lets library developers update your codebase without breaking it.&lt;/p&gt;

&lt;h1&gt;
  
  
  What's inside
&lt;/h1&gt;

&lt;p&gt;Once installeed you'll end up with several new files. The most important one is &lt;code&gt;app/guard/ability.ts&lt;/code&gt;. This file is the core of your authorization logic, and it will serve as a single source of truth, whether a user can or cannot do things in your app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GuardBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PrismaModelsType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@blitz-guard/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GetShoppingCartInput&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/shoppingCarts/queries/getShoppingCart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ExtendedResourceTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PrismaModelsType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ExtendedAbilityTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Guard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GuardBuilder&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ExtendedResourceTypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ExtendedAbilityTypes&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cannot&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cannot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;manage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$isAuthorized&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shoppingCart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;where&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;GetShoppingCartInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Guard&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ability&lt;/code&gt; file creates a &lt;code&gt;Guard&lt;/code&gt; using the &lt;code&gt;GuardBuilder&lt;/code&gt; function. The resulting guard will be executed on every authorized query or mutation.&lt;/p&gt;

&lt;p&gt;For example, if we want to deny access to a shopping cart based on who's the owner. We can do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/guard/ability.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GetShoppingCartInput&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/shoppingCart/queries/getShoppingCart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$isAuthorized&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shoppingCart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;where&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;GetShoppingCartInput&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;;&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;&lt;small&gt;Notice GetShoppingCartInput is the same TS type that we use in the getShoppingCart query&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;can&lt;/code&gt; (and &lt;code&gt;cannot&lt;/code&gt;) methods have three parameters. The first is the action to be performed (either create, read, update, delete, or manage), the second is which Prisma schema object this guard applies to (it doesn't need to be Prisma dependent, you can extend it using the &lt;code&gt;ExtendedResourceTypes&lt;/code&gt;), and the third is an async function that should resolve to a boolean. &lt;/p&gt;

&lt;p&gt;On this third argument you can implement any logic you want -like querying the DB- to assert whether the logged-in user has permissions to perform the action on the specified resource. A more interesting example could be the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$isAuthorized&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shoppingCart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;where&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;GetShoppingCartInput&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shoppingCartShare&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;shoppingCartId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;where&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="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&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;&lt;small&gt;☝️ Here we assert that the user is the shopping cart owner or that the cart has been shared with this user previously.&lt;/small&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Authorizing functions
&lt;/h1&gt;

&lt;p&gt;So far, so good, but changing the ability file will do nothing if we don't authorize our functions. Blitz Guard will intercept your functions call and execute the guard only if we wrap queries and mutations with the &lt;code&gt;authorize&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NotFoundError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blitz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Prisma&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Guard&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app/guard/ability&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GetShoppingCartInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Pick&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ShoppingCartFindFirstArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;where&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nx"&gt;getShoppingCart&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;where&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;GetShoppingCartInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$authorize&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;cart&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shoppingCart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findFirst&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;where&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;NotFoundError&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;cart&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Guard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shoppingCart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getShoppingCart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first two arguments of this function should look familiar because they are the same arguments &lt;code&gt;can&lt;/code&gt; and &lt;code&gt;cannot&lt;/code&gt; functions receive. The third argument is the function we want to wrap, and that will only be called if the guard criteria are met. &lt;em&gt;Otherwise, you'll get a 403&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This step is easy to forget, more so when the Blitz generator default exports the generated function. To aid this, Blitz Guard installs a middleware that warns you (in development) about queries and mutations not wrapped by the &lt;code&gt;Guard.authorize&lt;/code&gt; function.&lt;/p&gt;

&lt;h1&gt;
  
  
  Final words
&lt;/h1&gt;

&lt;p&gt;Blitz Guard is still under heavy development, but I don't think the API will change a lot. It's a great option if you plan to develop an ambitious Blitz app by moving scattered business logic into a single place.&lt;/p&gt;

</description>
      <category>blitz</category>
      <category>javascript</category>
      <category>monolith</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Submit forms without using re-captcha</title>
      <dc:creator>Gabriel Chertok</dc:creator>
      <pubDate>Thu, 24 Sep 2020 14:02:03 +0000</pubDate>
      <link>https://dev.to/iamcherta/submit-forms-without-using-re-captcha-2o8p</link>
      <guid>https://dev.to/iamcherta/submit-forms-without-using-re-captcha-2o8p</guid>
      <description>&lt;p&gt;If you ever had to put a form on a public page, you know bots will find it and send you "service offerings", newsletters, direct emails, and much more. That's exactly what happened the first time I deployed the Ingenious contact form.&lt;/p&gt;

&lt;p&gt;Opening a gateway to an inbox is problematic because, like everything in security, attackers -spammers in this case- have all the time in the world, and they can afford to do it wrong a million times until they nail it.&lt;/p&gt;

&lt;p&gt;To fix this problem, some developers use re-captcha, a tool that &lt;em&gt;"[...]uses an advanced risk analysis engine and adaptive challenges to keep malicious software from engaging in abusive activities on your website"&lt;/em&gt; 🥱. In plain English it &lt;strong&gt;keeps bots away from your forms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are a lot of great wrappers depending on the technology you are using. At Ingenious, we use Next.js and deploy our website to Vercel. If I wanted, I could have implemented some re-captcha validation on our contact form with an already existing npm package, but the sole idea of adding a library for something that trivial didn't sound right.&lt;/p&gt;

&lt;p&gt;Looking for alternatives, I learned about honeypots. Honeypots are additional inputs you put on a form to make bots think they are submitting correct info. The idea is to give the bot a honeypot field that looks legit and hide it with CSS from the users. On the backend, we can check if honeypot fields were submitted and discard that submission.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;ContactForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;onSubmit&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Contact Us&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* This is for the bot */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"honey"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Name&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;autoComplete&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"off"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"honey"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;autoComplete&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"off"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"honey"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Message&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt; &lt;span class="na"&gt;autoComplete&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"off"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* This is for real users */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name89jhbg2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Name&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name89jhbg2"&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name89jhbg2"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email789miu82"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email789miu82"&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email789miu82"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"message342cdssf3"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Message&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"message342cdssf3"&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"message342cdssf3"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Send&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt; &lt;span class="na"&gt;jsx&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`
        .honey {
          display: none;
        }
      `&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&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;Another technique I've used is to delay the rendering of the form several seconds after the page itself is rendered. My thinking behind this is that bots may or may not execute JS -likely they do- but I don't think they will wait more than 3 or 4 seconds. On the other hand, users don't need to see the form until they are way down on the page -the contact form in our case is close to the bottom of the page. By the time user has scrolled to the bottom, the form will be loaded already.&lt;/p&gt;

&lt;p&gt;When working with Next.js, you will use the &lt;code&gt;next/dynamic&lt;/code&gt; package that's somehow similar to the &lt;code&gt;React.lazy&lt;/code&gt; functionality. The idea is to dynamically &lt;code&gt;import&lt;/code&gt; a module creating a new chunk. Next.js will then fetch the module at runtime.&lt;/p&gt;

&lt;p&gt;Importing a module returns a promise that we can delay. In the case of Next.js, we need to ask for the module to be client-side only with &lt;code&gt;ssr: false&lt;/code&gt;, otherwise it will end up on the statically generated page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/dynamic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../utils&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;ContactForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../components/contact-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;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ssr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;IndexPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ContactForm&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&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;Lastly, we can tell Next.js to use a placeholder component while loading the dynamically imported one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/dynamic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../utils&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;ContactFormPlaceholder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Nice Spinner&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;ContactForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../components/contact-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;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ssr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ContactFormPlaceholder&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&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;This technique may hurt SEO, but how many times we need SEO for a contact form? The whole point is to allow real users to submit the form, not bots, even GoogleBot. &lt;/p&gt;

&lt;h2&gt;
  
  
  Here's the full example
&lt;/h2&gt;

&lt;p&gt;You can reload the codesandbox, and scroll down to the bottom to se the form placeholder before the actual form loads, and click the "Show hidden fields" checkbox to try submitting the form as a bot.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/contact-form-p9u6b"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Blitz.js a fullstack framework for the serverless era</title>
      <dc:creator>Gabriel Chertok</dc:creator>
      <pubDate>Fri, 04 Sep 2020 12:51:59 +0000</pubDate>
      <link>https://dev.to/iamcherta/blitz-js-a-fullstack-framework-for-the-serverless-era-493j</link>
      <guid>https://dev.to/iamcherta/blitz-js-a-fullstack-framework-for-the-serverless-era-493j</guid>
      <description>&lt;p&gt;Earlier this year, in a previous article, I push back on going serverless. Not because I think serverless is bad, but because the current serverless trend involves some practices that I don't think are useful for 95% of the apps that get built out there. &lt;/p&gt;

&lt;p&gt;If you want to detour here's the previous article 👇 I'll wait here drinking my 🧉.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/iamcherta" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F51220%2Fe85f52c0-7de2-4c35-8a85-b11902932737.jpg" alt="iamcherta"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/iamcherta/my-monolith-doesn-t-fit-in-your-serverless-311o" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;My monolith doesn't fit in your serverless&lt;/h2&gt;
      &lt;h3&gt;Gabriel Chertok ・ Jul 28 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#serverless&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ruby&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#monolith&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
 

&lt;p&gt;&lt;br&gt;&lt;br&gt;
👋 Welcome back! As I was saying, I still think the same way. I still feel we need full stack frameworks instead of 15 specialized tools that you can consume from a front-end, and I understand where this pressure of &lt;strong&gt;using the right tool for the job&lt;/strong&gt; comes from, but sometimes &lt;a href="https://www.swyx.io/writing/hammers/" rel="noopener noreferrer"&gt;a hammer is good enough&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hopefully, today I can marry these two worlds. Benefiting from serverless infrastructure while developing with a full-stack framework, as if you were writing Django or Ruby on Rails. Let's explore &lt;a href="https://blitzjs.com/" rel="noopener noreferrer"&gt;Blitz.js&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Enter Blitz.js
&lt;/h2&gt;

&lt;p&gt;Blitz.js is a full-stack framework adapted for the serverless era. It carries all the benefits of serverless ready frameworks like Next.js -it's built on top of it- while adopting features like a data layer or a set of reasonable defaults.&lt;/p&gt;

&lt;p&gt;Blitz.js is built on top of Next.js supporting most, if not all, Next.js features such as React for the view layer, Server Side Rendering (SSR), Static Site Generation (SSG), and the new Incremental Site Generation (ISG), but I feel the exciting parts are in the differences.&lt;/p&gt;
&lt;h2&gt;
  
  
  Serverless era?
&lt;/h2&gt;

&lt;p&gt;Currently, full-stack frameworks can't run on platforms like AWS lambda or Vercel. These platforms can support different languages like ruby, Java, or PHP, but the full-stack frameworks' programming model doesn't play nicely with the constraints FaaS exposes.&lt;/p&gt;

&lt;p&gt;Blitz.js embraces the FaaS constraints. You have no controllers, but stateless functions that can be executed as a long-running nodejs process or invoked as a lambda function.&lt;/p&gt;
&lt;h2&gt;
  
  
  Typescript
&lt;/h2&gt;

&lt;p&gt;By default, Blitz.js wants you to use Typescript: you can opt-out, but I wouldn't recommend it. TypeScript is a solid language, and the framework generators and all the internals are written in this language.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code organization
&lt;/h2&gt;

&lt;p&gt;While Next.js doesn't hold too many opinions, maybe non outside how to do routing, Blitz.js does.&lt;/p&gt;

&lt;p&gt;First, it encourages you to group files by functionality and not by role. If you have worked with a full-stack framework before, you know a big part of the framework's responsibility is to make these decisions for you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;├── app
│   ├── components
│   ├── layouts
│   ├── pages
│   │   ├── _app.tsx
│   │   ├── _document.tsx
│   │   └── index.tsx
│   ├── products
│   │   ├── components
│   │   │   └── ProductForm.tsx
│   │   ├── mutations
│   │   │   ├── createProduct.ts
│   │   │   ├── deleteProduct.ts
│   │   │   └── updateProduct.ts
│   │   ├── pages
│   │   │   └── products
│   │   └── queries
│   │       ├── getProduct.ts
│   │       └── getProducts.ts
│   └── queries
│       └── getReferer.ts
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Routes
&lt;/h2&gt;

&lt;p&gt;Here you see how &lt;code&gt;products&lt;/code&gt; and &lt;code&gt;app&lt;/code&gt; have both a &lt;code&gt;pages&lt;/code&gt; directory. At runtime, all these routes are smashed together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Queries &amp;amp; mutations
&lt;/h2&gt;

&lt;p&gt;Besides pages, we see other types of files, such as queries and mutations. Let's explain those.&lt;/p&gt;

&lt;p&gt;Queries and mutations are what you would expect, a way to query and store data from/to your database. While it's not restricted to the DB layer, it's probably the most common scenario.&lt;/p&gt;

&lt;p&gt;Blitz.js uses Prisma 2, a framework to abstract the interactions with the database, and it's used like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GetCompaniesInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;FindManyCompanyArgs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;where&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;getCompanies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;orderBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;asc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;GetCompaniesInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&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;companies&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;companies&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Queries -and mutations- are not API endpoints, but regular TS functions that you can import from your components and call. This is a novel concept I haven't seen in any other frameworks, called Zero-API.&lt;/p&gt;

&lt;p&gt;The idea behind the Zero-API is to allow you to call a function from a React component, while &lt;strong&gt;swapping that call at compile time for an API request&lt;/strong&gt;. This results in a simpler programming model. Importing and calling vs. dealing with endpoints, with the added benefit of TS type checking inputs and results. The framework makes the heavy lift for us at build time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Companies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;companies&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getCompanies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"font-bold text-4xl mb-8"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Companies&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;companies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Company&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&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;Queries are called from the front-end with a &lt;code&gt;useQuery&lt;/code&gt; hook. For mutations, no hook is needed you can just &lt;code&gt;await&lt;/code&gt; the mutation response. Also, types are carried over from the hook to your variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prisma 2
&lt;/h2&gt;

&lt;p&gt;We talked about Prisma 2 when discussing queries and mutations, but it deserves a bit more explanation. At its core, Prisma is a set of packages that allows you to interact with relational databases using node or TypeScript.&lt;/p&gt;

&lt;p&gt;If you choose TypeScript as Blitz does this, you get complete type safety for your models and DB operations, since Prisma will generate not only model types but types for querying and mutating the resource.&lt;/p&gt;

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

&lt;p&gt;The way Prisma works is by having a schema file with a custom DSL. This schema is similar to the one you can find in Rails, but instead of being the result of applying migrations it operates as the source of truth, and migrations are generated from this file.&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;datasource&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sqlite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postgres&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;generator&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prisma-client-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;// --------------------------------------&lt;/span&gt;

&lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="nx"&gt;Company&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;               &lt;span class="nx"&gt;Int&lt;/span&gt;      &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;autoincrement&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;createdAt&lt;/span&gt;        &lt;span class="nx"&gt;DateTime&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="nx"&gt;updatedAt&lt;/span&gt;        &lt;span class="nx"&gt;DateTime&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;updatedAt&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;             &lt;span class="nb"&gt;String&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;      &lt;span class="nb"&gt;String&lt;/span&gt;
  &lt;span class="nx"&gt;logo&lt;/span&gt;             &lt;span class="nb"&gt;String&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;              &lt;span class="nb"&gt;String&lt;/span&gt;   &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;hasOffices&lt;/span&gt;       &lt;span class="nb"&gt;Boolean&lt;/span&gt;
  &lt;span class="nx"&gt;allowsFullRemote&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;After you run the &lt;code&gt;blitz db migrate&lt;/code&gt; command, Prisma will generate a migration -a snapshot of the actual schema- and a Prisma client. A Prisma client is the package we use to interact with the DB and has the generated types for our schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  CLI
&lt;/h2&gt;

&lt;p&gt;Most of the things I talked about here can be created though the &lt;a href="https://blitzjs.com/docs/cli-overview" rel="noopener noreferrer"&gt;Blitz CLI&lt;/a&gt;. Currently, it has almost everything you need to start working with the framework such as &lt;code&gt;blitz new {PROJECT NAME}&lt;/code&gt; or &lt;code&gt;blitz generate&lt;/code&gt; to generate models, scaffolds pages and more, as well as the &lt;code&gt;blitz db&lt;/code&gt; command to interact with Prisma using the same CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final words
&lt;/h2&gt;

&lt;p&gt;There are many more things I wish I had covered in this review, such as the new &lt;a href="https://github.com/blitz-js/blitz/pull/678" rel="noopener noreferrer"&gt;upcoming seed command&lt;/a&gt;, the built-in &lt;a href="https://blitzjs.com/docs/auth" rel="noopener noreferrer"&gt;authentication&lt;/a&gt; or the &lt;a href="https://blitzjs.com/docs/using-recipes" rel="noopener noreferrer"&gt;recipes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I will be writing more about Blitz since I'm using it to rebuild &lt;a href="https://github.com/cherta/remoteuy" rel="noopener noreferrer"&gt;remote.uy&lt;/a&gt;, so hopefully, I can cover more ground and learn since I'm not an expert on the subject, and the framework is rapidly evolving.&lt;/p&gt;

&lt;p&gt;If you liked the framework, give it a try, and join the &lt;a href="https://slack.blitzjs.com/" rel="noopener noreferrer"&gt;Slack community&lt;/a&gt; where most of the action takes place.&lt;/p&gt;




&lt;p&gt;Liked the post? Shout out to &lt;a href="https://twitter.com/gusma" rel="noopener noreferrer"&gt;Gustavo&lt;/a&gt;, &lt;a href="https://twitter.com/FranCorreaSosa" rel="noopener noreferrer"&gt;Franco&lt;/a&gt;, &lt;a href="https://twitter.com/pepesenaris" rel="noopener noreferrer"&gt;Pepe&lt;/a&gt;, and &lt;a href="https://twitter.com/rsosenke" rel="noopener noreferrer"&gt;Ruben&lt;/a&gt; that helped me edit and refine this article.&lt;/p&gt;

</description>
      <category>react</category>
      <category>serverless</category>
      <category>javascript</category>
      <category>blitzjs</category>
    </item>
    <item>
      <title>Avoid JIRA like the plague</title>
      <dc:creator>Gabriel Chertok</dc:creator>
      <pubDate>Tue, 18 Aug 2020 18:51:00 +0000</pubDate>
      <link>https://dev.to/iamcherta/avoid-jira-like-the-plague-18f8</link>
      <guid>https://dev.to/iamcherta/avoid-jira-like-the-plague-18f8</guid>
      <description>&lt;p&gt;&lt;small&gt;Photo by &lt;a href="https://unsplash.com/@lucasrosin?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Lucas Rosin&lt;/a&gt; on &lt;a href="https://unsplash.com/t/textures-patterns?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;It's 2020, and JIRA doesn't allow you to archive projects, nor to export them, nor having feature parity with his own on-premises version. It's pretty clear. I dislike the tool, but these are not the main reasons we should avoid using it.&lt;/p&gt;

&lt;p&gt;I work at a &lt;a href="https://www.ingenious.agency"&gt;software development agency&lt;/a&gt; that executes projects for clients, mostly in the healthcare industry. We use JIRA a lot, including reporting, sprint planning, backlog grooming, addons, plugins, etc., and it has served us well all these years. But as we grew as a team, and as we seek perfection in how we execute projects I realized that the mere nature of the tool, this is, being prepared to work on small, discrete pieces of information (call them tickets, stories, bugs, etc.) is the very thing that slows us down.&lt;/p&gt;

&lt;h1&gt;
  
  
  What's JIRA
&lt;/h1&gt;

&lt;p&gt;When using a tool, it's essential to understand what it's optimized for. For me, JIRA is optimized for chunking features into smaller pieces, relating them together, and keeping a log of who did what when. This is great when you have something to chunk, but &lt;strong&gt;most often than not, there's nothing to chunk yet&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We use JIRA -or any similar software- before understanding what to do. I've seen projects without any documentation, but hundreds of tasks on a board. You may think it's not the tool's fault, but the leader's for adding such tasks before knowing what to build, I'll argue that those leaders were left with a tool that his sole input are tickets, so they figured their job was to feed it with lots of them.&lt;/p&gt;

&lt;h1&gt;
  
  
  What's the problem with it
&lt;/h1&gt;

&lt;p&gt;The tools we use shape how we work, and it turns out that we don't need a ticketing system, or at least, we don't need it for starting a project. I'll argue that we don't even need it for executing it.&lt;/p&gt;

&lt;p&gt;To develop better software aligned with our clients' and users' requirements, we must understand their needs and use tools that favor the narrative. A tool that puts everybody in context and describes intentions. We need &lt;strong&gt;software that's able to tell a story instead of dividing it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I feel so much time is wasted in ticketing. Bookkeeping, triaging and answering questions that at the end, the team is left only with fragmented pieces of reality and, too often, contradictory ones.&lt;/p&gt;

&lt;p&gt;Being required to write a story that provides context and communicates the intention for feature X is much harder than creating 20 tickets with incomplete and contradictory ideas, and then, leaving the engineers to figure out the rest. With luck, they will figure out before writing the code, most of the time not. But that's ok, we can always create more tickets now with a better understanding of the world.&lt;/p&gt;

&lt;h1&gt;
  
  
  What to do then
&lt;/h1&gt;

&lt;p&gt;I prefer to specify context-aware features that tell a story and illustrates the intention instead of splitting a feature into multiple tasks early in the process. &lt;/p&gt;

&lt;p&gt;It requires more dedication from product managers, more writing, and thinking profoundly and upfront about the problems that need to be solved. Engineers also dislike the idea of not having everything arranged in tickets, but rather a massive piece of text that describes the problem instead of the steps to solve it. &lt;/p&gt;

&lt;p&gt;Why doing something that everyone thinks it's worst? Well...when choosing to tell instead of splitting the problem, we are creating a more cohesive story, that makes sense as a whole, and it challenges itself as well. All this doesn't happen with tickets, they are artificial boundaries.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to do it
&lt;/h1&gt;

&lt;p&gt;I'm starting to write long-form feature definitions in Notion -I'm also writing this post in Notion-. Being deliberately slow at writing them and letting the concepts sink for more than a couple of days before making it available to the team. I try to be very explicit about the context and what's the takeaway for the user.&lt;/p&gt;

&lt;p&gt;Only after this exercise I chunk the work -or let the engineers chunk it themselves- with a Notion inline database inside the feature definition, so devs can move things around. Still, this database is always constrained to the main feature description, you cannot look at one without looking at the other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jwq88OgA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/6elrllgbb0sbxzsfixro.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jwq88OgA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/6elrllgbb0sbxzsfixro.gif" alt="Alt Text" width="768" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So far it works for me, and for the handful of people trying it, but my goal is to be able to make the switch out of JIRA, most importantly, out of the ticketing mindset. Yes, I lose advanced reporting, and yes, it requires more discipline from everybody in the team, but the gains are higher. Product managers stop splitting things early on and default to telling a story, engineers, on the other hand, are tasked with understanding the story and only then break them as they see fit.&lt;/p&gt;

&lt;p&gt;Hope you liked it, and please let's discuss 👇&lt;/p&gt;




&lt;p&gt;&lt;small&gt;Thanks to &lt;a href="https://twitter.com/gabobarre"&gt;Gabo&lt;/a&gt;, &lt;a href="https://twitter.com/elmasse"&gt;Masse&lt;/a&gt;, &lt;a href="https://twitter.com/gusma"&gt;Gustavo&lt;/a&gt;, and &lt;a href="https://twitter.com/asfourco"&gt;Fadi&lt;/a&gt; for reviewing the post and helping me organize these ideas.&lt;/small&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>management</category>
    </item>
    <item>
      <title>My monolith doesn't fit in your serverless</title>
      <dc:creator>Gabriel Chertok</dc:creator>
      <pubDate>Tue, 28 Jul 2020 18:59:54 +0000</pubDate>
      <link>https://dev.to/iamcherta/my-monolith-doesn-t-fit-in-your-serverless-311o</link>
      <guid>https://dev.to/iamcherta/my-monolith-doesn-t-fit-in-your-serverless-311o</guid>
      <description>&lt;p&gt;Serverless is the future, no doubts about that, and I love the model. For a small agency like &lt;a href="https://www.ingenious.agency" rel="noopener noreferrer"&gt;Ingenious&lt;/a&gt; it helps us reduce costs and allow us to forget about infrastructure.&lt;/p&gt;

&lt;p&gt;As cool as serverless is, I found myself always needing to go an extra mile to deploy a complete solution, and it's not for the lack of tools. I have come to the conclusion that the problems I'm tasked to solve are tricky to get right using a serverless approach. Here's my take on why not serverles-all-the-things.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's serverless?
&lt;/h2&gt;

&lt;p&gt;First, let's scope what we say when we speak about serverless. A definition I like -that may be incomplete- is the following: &lt;em&gt;"Serverless is the ability to scale up, but also to scale down to 0"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I would add that typically serverless comes in the flavor of specialized services. For example, if you do a serverless app to store and retrieve things from a database, you may need at least functions, datastore, and authentication services. Probably also some background job processing, CDNs, etc.&lt;/p&gt;

&lt;p&gt;In theory, this sounds amazing, having all these discrete parts that do only one thing, and one thing well sounds appealing. I used to think serverless would solve most of our problems and that we will be able to write the frontend and author some functions to glue the different services together.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8aiktz414jq8gsdzyjt1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8aiktz414jq8gsdzyjt1.png" alt="The magical world of cherta that doesn't exist"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  I don't think that anymore
&lt;/h2&gt;

&lt;p&gt;Sadly, I don't think that's the case. At least not for the apps I build, and I would argue that's also the case for many of you.&lt;/p&gt;

&lt;p&gt;Stitching services together sounds excellent in theory, but it has its own complexity. I'll try breaking down a list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lack of conventions
&lt;/h3&gt;

&lt;p&gt;When developing a monolith, either RoR, Laravel, Django, or any other tool, you have a common way of modeling the problems, and this philosophy sticks during the development process. I think I would know how to use Rails ActionMailbox even if I never used it in the past, the framework is coherent and I know what to expect.&lt;/p&gt;

&lt;p&gt;This doesn't happen when you use Service A for one thing and Service B for another. There's some extra work your brain needs to do when moving between specialized services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Event-driven programming is hard
&lt;/h3&gt;

&lt;p&gt;At the very core, whenever you need to use two services together and complement some missing functionality with a lambda function, you are doing event driven programming. This is, an action performed in service A may trigger a function to execute and impact your datastore, or to send an email, etc. &lt;/p&gt;

&lt;p&gt;This model is hard to follow, things end up in a database, or in a queue without apparent connection. Similar to what happens on a monolith with model callbacks where you start getting effects that do not follow an evident cause.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: How to decide
&lt;/h2&gt;

&lt;p&gt;As usual, it depends. When the endeavor is huge, and you are building an app that needs to scale rapidly and painlessly, I think you should definitely consider serverless. Here service orchestration costs are less than what the services itself will offer in terms of scalability, reliability, etc. &lt;/p&gt;

&lt;p&gt;The same is true when tradeoffs are minimal. Imagine a small website like Ingenious's website or a marketing site. Those have not that many moving pieces, so I think it's ok to liberate myself from thinking about the infrastructure.&lt;/p&gt;

&lt;p&gt;But, here's the catch. Most of the apps we develop, and when I say we, I mean, most of the people I know that work on the industry -sorry, I don't have any friends at Google 🤷- are in an uncomfortable middle ground.&lt;/p&gt;

&lt;p&gt;Their app is probably not that big that needs to scale to infinity. Neither is so small that you can hold the architecture in your head all the time. &lt;/p&gt;

&lt;p&gt;Most of the apps I've worked on are fine running on Heroku dynos or having a few powerful DO boxes. Yeah, that may be a bit expensive, and I'm sure you can cut the bill by 50% if using serverless, but is it really necessary? What things are you trading off by doing that?&lt;/p&gt;

&lt;p&gt;Too many times, I end up answering that it isn't worth changing. I may be getting old 👴🧉, but I prefer to be in control and to keep the same conventions thru my app even if I need to pay a bit more.&lt;/p&gt;

&lt;p&gt;With that being said, I also think this will change rapidly. Both, technologies, and serverless services will end up providing full-stack frameworks that will mix the best of both worlds. I think &lt;a href="https://blitzjs.com/" rel="noopener noreferrer"&gt;Blitz.js&lt;/a&gt; heads in that direction, and it will be interesting to see how it evolves.&lt;/p&gt;

&lt;p&gt;For now, I'm sticking with monoliths and plain-old-servers for the apps I need to maintain, but hopefully not for too long.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>javascript</category>
      <category>ruby</category>
      <category>monolith</category>
    </item>
    <item>
      <title>Technology-wise, where is Ingenious going?</title>
      <dc:creator>Gabriel Chertok</dc:creator>
      <pubDate>Tue, 14 Jul 2020 01:29:19 +0000</pubDate>
      <link>https://dev.to/iamcherta/technology-wise-where-is-ingenious-going-5hb</link>
      <guid>https://dev.to/iamcherta/technology-wise-where-is-ingenious-going-5hb</guid>
      <description>&lt;h1&gt;
  
  
  Disclaimer
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;The following is a post I wrote for the &lt;a href="https://www.ingenious.agency"&gt;Ingenious&lt;/a&gt; team a few weeks ago. I wanted to give some guidance about what to expect in terms of tech decisions in the future. It is not that we haven't thought about this before, but the last time we did it was some years ago, and from there, we kept going with the flow. I took a step back and tried putting what I currently believe in this post.&lt;/p&gt;

&lt;p&gt;We are a software development agency that helps to build Healthcare products using our own approach &lt;a href="https://www.ingenious.agency/behavioral-insights"&gt;"Behavioral Design"&lt;/a&gt; based on Behavioral Economics concepts. For the sake of this post, you can think of Ingenious as a regular boutique development agency.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Original post
&lt;/h1&gt;

&lt;p&gt;When I joined Ingenious, more than 6 years ago, the idea was to help the team shifting the technology foundations of the company, I ended up doing much more, but I want to focus on the technology side of things today. Back then, Ingenious was not Ingenious, but Ingenious Softworks and I found an amazing team of experienced .net developers.&lt;/p&gt;

&lt;p&gt;Slowly, as I started to bring my own ideas and experiences to the table, I was able to convince some of you that open source software was the right choice and a smart move if what we were going to build were web applications. I brought my own years of Ruby on Rails experience and my passion for JavaScript, and from there it took off.&lt;/p&gt;

&lt;p&gt;Later, when the whole JS ecosystem blew off, we were already in a position to take advantage of it. We picked Ember as a frontend framework for My Fertility -they still use it- we also picked React for some early projects for Denver Health. It was official, we were making Single Page Applications, we still do them, but more importantly, we embraced the UI component model.&lt;/p&gt;

&lt;h1&gt;
  
  
  Timeline
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ekFsuBDa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/bxsn5w1cnkfk0kli3m2s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ekFsuBDa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/bxsn5w1cnkfk0kli3m2s.png" alt="Alt Text" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While this seems natural, at one point we decided -we Ingenious, we the developer community, this is a big we- that to be able to develop application UIs using components -this is, mixing together HTML, JS, and CSS in a single unit- it was necessary to create a new, separate application.&lt;/p&gt;

&lt;p&gt;To a certain degree, this makes sense, when React -and every other framework came out- SSR was an optimization left out for later, for example, there was no way in Ember to do SSR when it first came out, neither React had one so it made sense to create its own application and communicate using JSON. Also, globally there was -still is- a trend around technology specialization. We never shared that vision that says that if I know the ins and outs of a certain technology, then I can be more productive with it and do more work in less time. I always thought that to deliver a feature, one should be able to work on both ends, even if that work is slower, it would be complete, and I hold to that belief today.&lt;/p&gt;

&lt;p&gt;All those reasons, and probably many more lead us to believe we needed to choose between a single monolith that treats complex views as second class citizens, or specialized apps that can leverage React, or Ember, or Vue, but that cannot co-exist in that monolith, and that's simply not true. It was not true back when we made these choices, and it's definitely not true today.&lt;/p&gt;

&lt;p&gt;In most of the cases, having multiple apps hurts productivity more than finding a way around the monolith and enhance it with JS. Having multiple apps generates a REST contract that needs to be held true for both ends, it creates the need to keep both apps in sync, it doesn't produce faster results, it generates stagnation and slower cycles since now there are at least two moving pieces, two codebases, two apps that can go down, two everything, that's 100% more problems.&lt;/p&gt;

&lt;p&gt;That's not to say sometimes you need two applications, sometimes splitting is required, even mandatory -thinking about mobile apps here- and there's no way around those scenarios, but when there is, we should think things carefully.&lt;/p&gt;

&lt;p&gt;In retrospective, I should have revised my decisions more carefully, I should have challenged the status quo more. Luckily, I have you that did a fantastic job regardless of those decisions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Moving On
&lt;/h1&gt;

&lt;p&gt;Moving on, we should think if the benefits of doing what everybody else is doing will benefit the project, will help the team, but most importantly, if it will benefit the client.&lt;/p&gt;

&lt;p&gt;My intention is not to categorically ban SPAs and React or Angular or Ember at the company, but to give you the chance to think about that part of the architecture more in-depth. This is me telling you that you can choose not to go with our standard approach, and this is me telling you that our standard approach may someday be not standard at all.&lt;/p&gt;

&lt;h1&gt;
  
  
  Alternatives
&lt;/h1&gt;

&lt;p&gt;You may be thinking, ok if we should consider sticking with the monolith more closely how should we build UIs from now on, do we go back and ditch the component model? No, I don't want to do that either, I think that's a step back.&lt;/p&gt;

&lt;p&gt;Luckily, other options put things in a nice middle ground. Some are more appealing, some feel dated, and some are new tech that we may consider using in the future.&lt;/p&gt;

&lt;p&gt;Here I list some options I've found for the current technologies we use, there should be plenty more that I'm missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ruby on Rails
&lt;/h2&gt;

&lt;p&gt;Rails has been our de-facto way for building backends at the company, and I think it will serve us for many more years. It has excellent features, and it's battle-tested. I think it also worth mention that Rails can do React or Angular, or Vue and some solutions even allow SSR and re-hydration.&lt;/p&gt;

&lt;p&gt;Here's a list of things that we may reach out in the future:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/webpacker"&gt;Rails Webpacker (does React, Vue, Elm, Svelte, Angular)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/reactjs/react-rails"&gt;React Rails (does SSR)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://boringrails.com/articles/hovercards-stimulus/"&gt;Stimulus + Turbolinks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.stimulusreflex.com/"&gt;Stimulus + Stimulus Reflect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Stimulus + Turbolinks + NEW MAGIC 👇&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--biY9SA2L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/43c7k2dzcgcl0teerg52.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--biY9SA2L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/43c7k2dzcgcl0teerg52.png" alt="Alt Text" width="301" height="643"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Phoenix
&lt;/h2&gt;

&lt;p&gt;Phoenix is a Rails like framework for the Elixir programming language. I won't enumerate the benefits of the functional approach elixir has, or how good the Erlang VM is, all that you can read elsewhere. The only thing that strikes me as I rethink best practices is that you can create nice interactive applications with it, you can mix presentation and behavior -ala React components- in structures called &lt;a href="http://blog.pthompson.org/liveview-livecomponents-introduction#liveview-livecomponents_1"&gt;LiveComponents&lt;/a&gt; without needing to split your app.&lt;/p&gt;

&lt;p&gt;How can Phoenix do this? It's not important for the sake of this discussion. The important thing is that you can create reusable components as monolith first-class citizens. The tradeoff is learning a new language with new paradigms, this is a tradeoff that we may be willing to do if we stick true to our previous learnings of having less moving pieces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blitz.js
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://blitzjs.com/"&gt;Blitz&lt;/a&gt; is a full-stack framework inspired by Ruby on Rails but written in React. It's based on Next.js and it can deploy itself as a fullstack application with a long process serving the app or in a serverless environment like AWS lambda or Vercel.&lt;/p&gt;

&lt;p&gt;The promise is to keep writing React components, import server side code functions and call them as if they were in the client. Blitz takes care of translating that to API calls.&lt;/p&gt;

&lt;h1&gt;
  
  
  What's next
&lt;/h1&gt;

&lt;p&gt;Ok, so this is all nice, but it's missing an actionable item, what to do next? I realize that you need certainty, not the half baked solution I just gave some paragraphs ago. Sadly, there's no silver bullet for developing software as there's no silver bullet that will fit all the projects we do at the company.&lt;/p&gt;

&lt;p&gt;From Qt apps that read CT scans to huge backends in Rails to more serverless oriented architectures we have covered a lot of ground in software development, and we will continue to do so in the best way we can, and similar to today's post there will be new ones enumerating things we believed were right but ended up being wrong decisions.&lt;/p&gt;

&lt;p&gt;For the moment, my current thinking is to try hard not to break the monolith unless it's absolutely necessary.&lt;/p&gt;

&lt;p&gt;No much more to say other than, stay current, and stay focused in the tech that better solves our problems; this sometimes means no to jump in the next hype bandwagon.&lt;/p&gt;

&lt;p&gt;-Cherta&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>management</category>
    </item>
    <item>
      <title>Generate authorized-short-living URLs in rails</title>
      <dc:creator>Gabriel Chertok</dc:creator>
      <pubDate>Sat, 10 Mar 2018 16:06:44 +0000</pubDate>
      <link>https://dev.to/iamcherta/generate-authorized-short-living-urls-in-rails--1cdo</link>
      <guid>https://dev.to/iamcherta/generate-authorized-short-living-urls-in-rails--1cdo</guid>
      <description>&lt;p&gt;Last week I got a small task for a project my team is working on. I don’t usually code that much lately, but this one seemed like a simple and straightforward one.&lt;/p&gt;

&lt;p&gt;The problem was straightforward, allow clients to download an on-the-fly generated file (a report) from our React frontend. As trivial as it may sound some interesting caveats made this problem not trivial at all.&lt;/p&gt;

&lt;p&gt;As you know, SPAs communicate with the backend using AJAX and most of the time authentication/authorization is done via some kind of header information. At Ingenious we use JWT a lot, and we love it.&lt;/p&gt;

&lt;p&gt;For our app, users need to be authorized and authenticated to get the report but &lt;em&gt;streaming a file as a response of an AJAX request only works for Chrome&lt;/em&gt;, all the other browsers ignore the response and don't pop up the save file dialog.&lt;/p&gt;

&lt;p&gt;The problem is now evident:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How to stream contents to the client with AJAX when there are authentication headers involved.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Start digging, stop digging
&lt;/h2&gt;

&lt;p&gt;As usual, I googled "js generate file from stream" because I thought it was the easiest solution, just grab what I already have working for Chrome and do the extra mile to make it work for all the other browsers.&lt;/p&gt;

&lt;p&gt;Maybe there’s even an npm package for that, but I quickly realized that this was not the right choice and that throwing npm packages to the problem won’t solve it, quite the opposite, it will make it difficult to read and error-prone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rethinking the problem
&lt;/h2&gt;

&lt;p&gt;My problem was not to stream contents via AJAX and generate a file out of that content but actually allow clients to download a file (which happens to be created on the fly) without compromising the app security, i.e., without opening a resource to the whole internet.&lt;/p&gt;

&lt;p&gt;With this new objective in mind, I re-imagined the file download as a two-step process.&lt;/p&gt;

&lt;p&gt;What if the client app request the document to be created and as the response, it gets a short-living URL for that resource.&lt;/p&gt;

&lt;p&gt;The idea was to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request a file "creation" from React and get back a signed short-living URL. This is an authenticated request.&lt;/li&gt;
&lt;li&gt;Using this URL, I can request the report in a new window without any extra headers (and thus without the usual authorization I use for my web app).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key was to generate a URL on step 1 that carries a token on the query string with an expiration date I can check on the “open” endpoint (step 2). So I looked for a solution that allows me to sign data and make it expire after X amount of time and guess what, JWT does precisely that.&lt;/p&gt;

&lt;p&gt;The only key difference is that I had to create a token and send it on a query string due to the impossibility to send headers when doing a &lt;code&gt;window.open&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;

&lt;p&gt;The previous code the app had was quite simple, we made an AJAX request and streamed with &lt;code&gt;send_data&lt;/code&gt; the contents of the file. Authorization / Authentication is done via Pundit / Knock on a &lt;code&gt;before_action&lt;/code&gt; hook.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ww1j27bg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2AL95vE78HUv4L1zoevMpcVQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ww1j27bg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2000/1%2AL95vE78HUv4L1zoevMpcVQ.png" alt="My old rails code" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This wasn't working for browsers other than Chrome so I split the process, first create a report URL that will live for 30 seconds and serve the file on that new URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K_C8KM6K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1600/1%2A3O8Oig25GO1K_HmEePTMMQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K_C8KM6K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1600/1%2A3O8Oig25GO1K_HmEePTMMQ.png" alt="The new create method" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We added a &lt;code&gt;create&lt;/code&gt; method to the controller. This method will be in charge of creating a short living URL using a signed JWT token that will expire in 30 seconds from now, I also encode the user id that's requesting the resource.&lt;/p&gt;

&lt;p&gt;The client will get a JSON object similar to this:&lt;code&gt;{url: "https://domain.com/reports/report_type?token=encryptedtoken"}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fqDiTbxr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1600/1%2AqDZwbFKHupObMgsvt2KICA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fqDiTbxr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1600/1%2AqDZwbFKHupObMgsvt2KICA.png" alt="The modified show version" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The show changes a bit, it skips the authentication and, the first thing it does is to decode the JWT token with &lt;code&gt;JWT.decode&lt;/code&gt;. &lt;code&gt;JWT.decode&lt;/code&gt; would throw a &lt;code&gt;JWT::ExpiredSignature&lt;/code&gt; if the token expired. I can then rescue from that error and return a 403 to my users if needed. I can also rescue from &lt;code&gt;JWT::DecodeError&lt;/code&gt; in case no token is given for example.&lt;/p&gt;

&lt;p&gt;If everything passes then, I know the URL was signed by me and that it's within the &lt;code&gt;exp&lt;/code&gt; time I set on the create method. I can later override the pundit_user and call my authorization method for an extra security layer.&lt;/p&gt;

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

&lt;p&gt;With this simple idea we can have authenticated, short-living URLs with an approach that's flexible enough to avoid rewrite huge parts of our client app. I hope you like the idea.&lt;/p&gt;




&lt;p&gt;Are you looking for a passionate team that can help you envision, design, and build amazing products? &lt;a href="http://ingenious.agency"&gt;Drop us a line&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  About Ingenious
&lt;/h2&gt;

&lt;p&gt;Ingenious is a distributed product design and software development agency with offices in Montevideo, Uruguay, and Denver, Colorado, and a team distributed in more than five countries. We create products and build software that people want to use for challenging industry segments like healthcare, education, and government.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>rails</category>
      <category>jwt</category>
    </item>
  </channel>
</rss>
