<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ kyw - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ kyw - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 07 May 2026 04:04:36 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/kyw/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Use CSS Flexbox to Break Elements Out of Their Containers ]]>
                </title>
                <description>
                    <![CDATA[ When you're building a webpage, sometimes you might want images or other elements to go beyond the boundaries of a container. So in this tutorial, I'll show you a couple ways you can accomplish this. To illustrate a situation when you might find this... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/break-out-elements-from-containers-using-flexbox/</link>
                <guid isPermaLink="false">66bb9089b0d3ac3d7acde3ff</guid>
                
                    <category>
                        <![CDATA[ css flex ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ kyw ]]>
                </dc:creator>
                <pubDate>Mon, 08 Jan 2024 18:15:47 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/pexels-dagmara-dombrovska-19707371.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you're building a webpage, sometimes you might want images or other elements to go beyond the boundaries of a container. So in this tutorial, I'll show you a couple ways you can accomplish this.</p>
<p>To illustrate a situation when you might find this useful, let's say we are given a webpage that looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/847shots_so.png" alt="Image" width="600" height="400" loading="lazy">
<em>A webpage whose contents span 100% of the window's width</em></p>
<p>As you will probably agree, that's not very pleasant to read. One of the things you can do is set a <code>max-width</code> on the container – in this case it's the <code>article</code> element so that it's "contained" in a more pleasing way:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">article</span> {
  <span class="hljs-attribute">max-width</span>: <span class="hljs-number">60ch</span>; <span class="hljs-comment">/* no more than 60 characters per line in this container */</span>
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/645shots_so-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Use max-width property to constrain the width of a container</em></p>
<p>That's better, but it's off to the left. So now, let's place our article text and image at the center of the page by setting <code>margin: 0 auto</code> on the container:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">article</span> {
  <span class="hljs-attribute">max-width</span>: <span class="hljs-number">60ch</span>;
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto; <span class="hljs-comment">/* center this container */</span>
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/945shots_so.png" alt="Image" width="600" height="400" loading="lazy">
<em>Center our article with margin: 0 auto</em></p>
<p>Much better now, right? To further optimize readability, we can tweak the font's size and line height as I have done in the playground below:</p>
<div class="embed-wrapper">
        <iframe width="100%" height="350" src="https://codepen.io/kilgarenone/embed/NWJPePO" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>
<p>Now, notice the width of the image: It has been constrained by the width of the container. What if you want the image (or any other element) to span the entire page (that is, full-bleed), like this?</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/868shots_so.png" alt="Image" width="600" height="400" loading="lazy">
<em>Example of a full-bleed layout</em></p>
<p>Or what if you just want the image to "break out" to have any width that is wider than its container, like this?</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/539shots_so.png" alt="Image" width="600" height="400" loading="lazy">
<em>An element breaking out to have a width wider than that of the container</em></p>
<p>Currently, as far as I know, there are two main ways to accomplish this:</p>
<ol>
<li>You can either do a manual <a target="_blank" href="https://css-tricks.com/full-width-containers-limited-width-parents/">horizontal offset</a> via negative <code>margin-left</code> and <code>translateX</code> to shift the image to the left, or </li>
<li>You can use <a target="_blank" href="https://www.bram.us/2017/06/20/breaking-elements-out-of-their-containers-in-css/">CSS Grid</a>.</li>
</ol>
<p>But to my mind, the first solution is hacky, and the second solution is heavy-handed.</p>
<p>In this article, I'll cover some simpler ways to do this.</p>
<h2 id="heading-setting-the-stage">Setting the Stage</h2>
<p>First of all, let's free up all the child elements inside the container. Instead of setting a width on the container, we set it to its child elements:</p>
<pre><code class="lang-css"><span class="hljs-comment">/* Instead of doing this
article {
  max-width: 60ch;
  margin: 0 auto;
}
*/</span>

<span class="hljs-comment">/* we set width on its direct child elements */</span>
<span class="hljs-selector-tag">article</span> &gt; * {
  <span class="hljs-attribute">max-width</span>: <span class="hljs-number">60ch</span>; <span class="hljs-comment">/* childs' width can't go wider than 60ch */</span>
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto; <span class="hljs-comment">/* center them */</span>
}
</code></pre>
<p><strong>Before:</strong></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/504shots_so.png" alt="Image" width="600" height="400" loading="lazy">
<em>All children can't grow beyond the confines of their container</em></p>
<p><strong>After:</strong></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/409shots_so.png" alt="Image" width="600" height="400" loading="lazy">
<em>Now each child has plenty of space to grow horizontally if it chooses to</em></p>
<p>The  <code>article</code> container is no longer limited to a specific width. It now spans the entire width of the window. This has allowed any child element to grow sideways until the boundary of the window.</p>
<p>For example, we can choose to specifically let the image to grow to our heart's content:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/4shots_so.png" alt="Image" width="600" height="400" loading="lazy">
<em>The <code>img</code> element growing wider than the <code>p</code> elements</em></p>
<p>This is exactly what we are going to learn how to do next.</p>
<h2 id="heading-how-to-break-out-childless-elements">How to Break Out Childless Elements</h2>
<p>Breaking out at its simplest form is when the elements we want to break out are childless. For example, say we want to break out the <code>img</code> element below:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Texts<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">img</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Texts<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
</code></pre>
<p>And let's make it <em>full-bleed</em>. To do that, we will apply 3 properties on the <code>img</code>:</p>
<ol>
<li><code>**display: block**</code> – Because a) <code>img</code> is inline by default, and b) <code>margin: 0 auto</code> only works on <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left#auto">block</a> elements. </li>
<li><code>**width: 100%**</code> – To have it fully fill the width of its container which is <code>article</code> that already fills the window.</li>
<li><code>**max-width: 100%**</code> – To override the <code>max-width: 60ch</code> and also to stop it from expanding beyond the available horizontal space of its container. </li>
</ol>
<pre><code class="lang-css"><span class="hljs-selector-tag">img</span> {
  <span class="hljs-attribute">display</span>: block;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">max-width</span>: <span class="hljs-number">100%</span>; 
}
</code></pre>
<p>And it works:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/914shots_so.png" alt="Image" width="600" height="400" loading="lazy">
<em>Full-bleed layout</em></p>
<p>Following up on the previous playground:</p>
<div class="embed-wrapper">
        <iframe width="100%" height="350" src="https://codepen.io/kilgarenone/embed/QWowRME" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>
<p>So far so good.</p>
<h2 id="heading-how-to-break-out-elements-that-have-children">How to Break Out Elements that Have Children</h2>
<p>Now, what if your image is wrapped in a <code>figure</code>, like this:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">figure</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"900px"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">""</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">""</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">figcaption</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">figcaption</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">figure</span>&gt;</span>
</code></pre>
<p>And the image has its width set or even just uses its intrinsic width? How will we break it out while centering it? </p>
<p>Without leveraging CSS Flexbox, to make the image full-bleed as before, first we need to set the <code>figure</code> to <code>max-width: 100%</code> so that it can fill the entire horizontal space of <code>article</code>. Next, we need to make sure our <code>img</code> is applied with <code>display: block</code> and <code>margin: 0 auto</code> in order to stay centered.</p>
<p>Compare this to if we were to use <strong>CSS Flexbox</strong>. Apply a CSS class that contains 3 Flexbox properties to <code>figure</code> alone and we are done:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.break-out</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-direction</span>: column;
  <span class="hljs-attribute">align-items</span>: center;
}
</code></pre>
<ol>
<li><code>**display: flex**</code> – Set <code>figure</code> as a Flex container from which we can choose to arrange its content – in this case they would be the <code>img</code> and <code>figcaption</code> – either horizontally (row) or vertically (column) along the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Basic_concepts_of_flexbox#the_main_axis">main axis</a>. </li>
<li><code>**flex-direction: column**</code> – For our use case, we choose to arrange the content vertically.</li>
<li><code>**align-items: center**</code> – Lastly, we choose to always center the content along the cross axis. If main axis is going vertically in <code>flex-direction: column</code> , its cross axis would be going horizontally. So this property allows <code>img</code> and <code>figcaption</code> to always be centered horizontally relative to <code>figure</code> which is now spanning 100% of the screen. </li>
</ol>
<p>Finally, we just apply the class to the <code>figure</code> element:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">figure</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"break-out"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"900px"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">""</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">""</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">figcaption</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">figcaption</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">figure</span>&gt;</span>
</code></pre>
<p>Now whatever the width of the <code>img</code> may be, it will be centered even until it's full-bleed:</p>
<div class="embed-wrapper">
        <iframe width="100%" height="350" src="https://codepen.io/kilgarenone/embed/eYXNyjp" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>
<h2 id="heading-benefits-of-these-methods">Benefits of These Methods</h2>
<p>The methods I have demonstrated here have the following benefits:</p>
<ol>
<li><a target="_blank" href="https://www.w3.org/2001/tag/doc/leastPower.html">Principle of Least Power</a>: CSS Flexbox is a less powerful solution than CSS Grid.</li>
<li>No complicated calculation: We let the browser center our image within the available space.</li>
<li>Responsive: No re-calculation needed on our part when the browser resizes.</li>
</ol>
<p>The second-order benefits are:</p>
<ol>
<li>For users: optimal performance – less code shipped and less expensive code to run. Happy browser, happy users.</li>
<li>For developers: simpler and less code to maintain, which means less valuable cognitive resources spent.</li>
</ol>
<h2 id="heading-background">Background</h2>
<p>I found this method when I was building Zuunote. It's a <a target="_blank" href="https://zuunote.com/">Markdown-based note-taking web app</a> in which images can be resized. </p>
<p>The tricky thing is, in Markdown, image syntax is parsed as an inline element. So, when users do create inline images when writing in a paragraph, this method enables them to resize between inline and full-bleed.</p>
<p>This is how I achieved it. Similar to what we just discussed, I wrapped the <code>img</code> element in a <code>span</code> to retain the inline characters:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">""</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">""</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
</code></pre>
<p>Then I applied our Flexbox properties on the <code>span</code> when the user has resized beyond the boundary of the paragraphs:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"break-out"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">""</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">""</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
</code></pre>
<p>And the browser will keep the image centered without any expensive hand-holding on my part.</p>
<p>Here is the result:</p>
<div class="embed-wrapper">
        <iframe width="100%" height="350" src="https://codepen.io/kilgarenone/embed/bGZdEQJ" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>
<p>One gesture covers a spectrum of resizing intentions – I think that's pretty neat :)</p>
<p>Thank you for reading! </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The SaaS Handbook – How to Build Your First Software-as-a-Service Product Step-By-Step ]]>
                </title>
                <description>
                    <![CDATA[ In this extensive write-up, I'll cover how all the main pieces came together for the first SaaS I ever launched. From implementing favicon to deploying to a cloud platform, I will share everything I learned. I'll also share extensive code snippets, b... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-your-first-saas/</link>
                <guid isPermaLink="false">66bb908dd2bda3e4315491d4</guid>
                
                    <category>
                        <![CDATA[ SaaS ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ kyw ]]>
                </dc:creator>
                <pubDate>Tue, 30 Jun 2020 15:52:15 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/07/The-SaaS-Handbook-Book-Cover--1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this extensive write-up, I'll cover how all the main pieces came together for the first SaaS I ever launched.</p>
<p>From implementing favicon to deploying to a cloud platform, I will share everything I learned. I'll also share extensive code snippets, best practices, lessons, guides, and key resources. </p>
<p>I hope something here will be useful to you. Thanks for reading. ❤️</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a href="#introduction">Introduction</a></li>
<li><a href="#findingideas">Finding Ideas</a></li>
<li><a href="#thestack">The Stack</a></li>
<li><a href="#repo">Repo</a><ul>
<li><a href="#startfullstackdevelopmentlocally">Start full-stack development locally</a></li>
</ul>
</li>
<li><a href="#client">Client</a><ul>
<li><a href="#npmscriptclient">Npm Script</a></li>
<li><a href="#environmentvariables">Environment Variables</a></li>
<li><a href="#webpackbabel">Webpack &amp; Babel</a></li>
<li><a href="#webperformance">Web Performance</a></li>
<li><a href="#differentialserving">Differential Serving</a></li>
<li><a href="#fonts">Fonts</a></li>
<li><a href="#icons">Icons</a></li>
<li><a href="#favicon">Favicon</a></li>
<li><a href="#apicalls">API Calls</a></li>
<li><a href="#testproductionbuildlocally">Test Production Build Locally</a></li>
<li><a href="#security">Security</a></li>
</ul>
</li>
<li><a href="#design">Design</a><ul>
<li><a href="#modularscale">Modular Scale</a></li>
<li><a href="#colors">Colors</a></li>
<li><a href="#cssreset">CSS Reset</a></li>
<li><a href="#astylingpractice">A Styling Practice</a></li>
<li><a href="#layout">Layout</a></li>
</ul>
</li>
<li><a href="#server">Server</a><ul>
<li><a href="#filestructure">File Structure</a></li>
<li><a href="#npmscriptserver">Npm Script</a></li>
<li><a href="#database">Database</a></li>
<li><a href="#creatingtableschemas">Creating Table Schemas</a></li>
<li><a href="#droppingadatabase">Dropping a database</a></li>
<li><a href="#craftingsqlqueries">Crafting SQL Queries</a></li>
<li><a href="#redis">Redis</a></li>
<li><a href="#errorhandlinglogging">Error Handling &amp; Logging</a></li>
<li><a href="#permalinkforurlsharing">Permalink for URL Sharing</a></li>
</ul>
</li>
<li><a href="#userauthenticationsystem">User Authentication System</a></li>
<li><a href="#email">Email</a><ul>
<li><a href="#implementation">Implementation</a></li>
<li><a href="#emailtemplates">Email Templates</a></li>
<li><a href="#howtogetoneofthosehiexamplecom">How to get one of those <code><strong>hi@example.com</strong></code></a></li>
</ul>
</li>
<li><a href="#tenancy">Tenancy</a></li>
<li><a href="#domainname">Domain Name</a><ul>
<li><a href="#howtogetoneofthoseappexamplecom">How to get one of those <code><strong>app.example.com</strong></code></a></li>
</ul>
</li>
<li><a href="#deployment">Deployment</a><ul>
<li><a href="#deploynodejs">Deploy Nodejs</a></li>
<li><a href="#deploypostgresql">Deploy Postgresql</a></li>
<li><a href="#setupschemasinproductiondatabase">Setup schemas in production database</a></li>
<li><a href="#deployredis">Deploy Redis</a></li>
<li><a href="#filestorage">File Storage</a></li>
<li><a href="#deploynewchangesinbackend">Deploy New Changes in Back-end</a></li>
</ul>
</li>
<li><a href="#hostingyourspa">Hosting Your SPA</a><ul>
<li><a href="#deploynewchangesinfrontend">Deploy New Changes in Front-end</a></li>
</ul>
</li>
<li><a href="#richtexteditor">Rich-text Editor</a></li>
<li><a href="#cors">CORS</a></li>
<li><a href="#paymentsubscription">Payment &amp; Subscription</a></li>
<li><a href="#landingpage">Landing Page</a></li>
<li><a href="#termsandconditions">Terms and Conditions</a></li>
<li><a href="#marketing">Marketing</a></li>
<li><a href="#wellbeing">Well-being</a></li>
</ul>
<h2 id="heading-introduction">Introduction</h2>
<p>I switched careers to web development back in 2013. I did it for two reasons. </p>
<p>First, I noticed I could get lost in building customer-facing products among all the colors and endless possibilities for interactivity. So while being reminded of the trite "<em>Find a job you enjoy doing, and you will never have to work a day in your life</em>", I thought "<em>Why not make this a job?</em>" </p>
<p>And second, I wanted to make something of myself, having spent my teenage years inspired by Web 2.0 (Digg.com circa 2005 opened the world for me!). The plan was to work on the latter while working in the former.</p>
<p>Turns out, though, that the job and the 'JavaScript fatigue' ensued and wholly consumed me. It also didn't help that I was reckless in my pursuit of my ambition, after being influenced by the rhetoric from 'Silicon Valley'. I read Paul Graham's <em>Hackers &amp; Painters</em> and Peter Thiel's <em>Zero to One</em>. I thought, I'm properly fired up! I'm hustling. I can do this too!</p>
<p>But nope, I couldn't. At least not alone. I was always beat after work. I couldn't find a team that shared my dreams and values. </p>
<p>So meanwhile, I rinsed and repeated less than half-baked projects in my free time. I was chronically anxious and depressed. I mellowed out as the years went by. And I began to cultivate a personal philosophy on entrepreneurship and technology that aligned better with my personality and life circumstances – until September 2019.</p>
<p>The fog in the path ahead finally cleared up. I got pretty good at it – the job then became less taxing, and I'd reined in my 'Javascript fatigue'. For the longest time, I had the mental energy, time, and the mindset that allowed me to see through a side project. And that time, I started small. I believed I had this!</p>
<p>I was wrong.</p>
<p>Since I had been a front-end developer for my entire career, I could only go as far as naming the things that I imagined I would need – a 'server', a 'database', an 'authentication' system, a 'host', a 'domain name', but <em>how</em>... <em>where</em>... and <em>what</em>...<em>I..I don..I don't even</em>... ?</p>
<p>Now, I knew my life would have been easier if I'd decided to use one of those abstract tools like 'create-react-app', 'firebase SDK', 'ORM', and 'one-click-deployment' services. The ode of '<em>Don't reinvent the wheel. Iterate fast</em>'. </p>
<p>But there were a few qualifications I wanted my decisions to meet:</p>
<ul>
<li>No vendor lock-in — This ruled out using the Firebase SDK all over my codebase. This included 'create-react-app', because ejecting it forced me to inherit and maintain its massive tooling infrastructure.</li>
<li>Simple &amp; Minimalistic — Cut having to learn new opinionated syntax and patterns. This ruled out 1) Project generators that output complex architecture and layers of boilerplate codes, 2) Using third-party libraries such as 'knex.js' or the 'sequelize' ORM.</li>
<li>Pay-as-you-need — I wanted to keep my operating cost proportional to the usage level. This ruled out services such as 'one-click-deployment'.</li>
</ul>
<p>To be fair, I had the following things going for me:</p>
<ul>
<li>I was building a simple SaaS.</li>
<li>I was not anxious to scale, dominate, disrupt etc.</li>
<li>I was still holding my day job.</li>
<li>I had accepted my odds of failure. ?</li>
</ul>
<p>Also keep in mind that:</p>
<ul>
<li>This was a one-man show—design, development, maintenance, marketing, etc.</li>
<li>I'm not a 10x rockstar full-stack programmer.</li>
</ul>
<p><strong>Most importantly</strong>, I wanted to follow through with a guiding principle: Building things <a target="_blank" href="https://alistapart.com/article/responsible-javascript-part-1/"><em>responsibly</em></a>. Although, unsurprisingly, doing so had had a significant impact on my development speed, and it forced me to clarify my motivations:</p>
<ul>
<li>If I had to ship something as soon as possible, unless it was a matter of life and death, then I probably wasn't solving a unique and hard problem. In that case—assuming I was still at my day job and had zero debt— What was the rush?</li>
<li>And second-guessing from the ethical perspective: Was it even a problem that needed solving? What would be the second-order consequences if I solved it? Could my good intentions be better directed elsewhere?</li>
</ul>
<p>So what follows in this article is everything I've learned while developing the first project I ever launched called <strong>Sametable</strong> that helps <a target="_blank" href="https://www.sametable.app">managing your work in spreadsheets</a>. </p>
<p>Let's get to it.</p>
<h2 id="heading-finding-ideas">Finding Ideas</h2>
<p>Well, first of all, you need to know what you want to build. I used to lose sleep over this, thinking about and remixing ideas, hoping for eureka moments, until I started to look inward:</p>
<ul>
<li>Build things that solve problems that you encounter and piss you off frequently.</li>
<li>Solve the so-called 'pain points' or 'frictions'. Go outside, don't stop listening to people and learn from them.</li>
<li>Be an expert in your domain. Feel its pains. Maybe solve one of them. Seems to me lots of founders founded company related to their domain on which they have built their career and social network.</li>
</ul>
<h2 id="heading-the-stack">The Stack</h2>
<p>How your stack looks will depend on how you want to render your application. Here is a <a target="_blank" href="https://developers.google.com/web/updates/2019/02/rendering-on-the-web#wrapup">comprehensive</a> discussion about that, but in a nutshell:</p>
<ul>
<li><p><strong>Client-side rendering(CSR); SPA; JSON APIs</strong> —
This is perhaps the most popular approach. It's great for building interactive web applications. But <a target="_blank" href="https://macwright.org/2020/05/10/spa-fatigue.html">be aware</a> of its downsides and steps to mitigate them. This is the approach I took, so we will talk about it in a lot of detail.</p>
</li>
<li><p><strong>Hybrid CSR; Both client-side and server-side rendering(SSR)</strong> —
With this approach, you still build your SPA. But when a user requests your app, for example, the homepage, you render the homepage's component into its static HTML <strong>in your server</strong> and serve it to the user. Then at the user's browser, <a target="_blank" href="https://reactjs.org/docs/react-dom.html#hydrate">hydration</a> will happen so the whole thing becomes the intended SPA.</p>
</li>
</ul>
<p>The main benefits of this approach are that you get good SEO and users can see your stuff sooner (faster 'First Meaningful Paint'). </p>
<p>But there are downsides too. Apart from the extra maintenance costs, we will have to download the same payload twice—First, the HTML, and second, its Javascript counterpart for the 'hydration' which will exert significant work on the browser's main thread. This prolongs the 'First time to interactive', and hence diminishes the benefits gained from a faster 'First meaningful paint'.</p>
<p>  The technologies that are adopted for this approach are <a target="_blank" href="https://nextjs.org/">NextJs</a>, <a target="_blank" href="https://nuxtjs.org/">NuxtJs</a>, and <a target="_blank" href="https://www.gatsbyjs.org/">GatsbyJs</a>.</p>
<ul>
<li><p><strong>Server-side rendering and 'sprinkle ✨ it with Javascript'</strong>
— This was the old-school way of building on the web!—Use PHP to build your templates with data in your server, then bind events handlers to the DOM with jQuery in the browser. This approach might have been ill-suited to build the increasingly complex apps that businesses have asked for on the web, but some technologies have emerged to warrant a reconsideration:</p>
<ul>
<li><a target="_blank" href="https://stimulusjs.org/">https://stimulusjs.org/</a></li>
<li><a target="_blank" href="https://github.com/turbolinks/turbolinks">https://github.com/turbolinks/turbolinks</a></li>
<li><a target="_blank" href="https://github.com/phoenixframework/phoenix_live_view">https://github.com/phoenixframework/phoenix_live_view</a></li>
<li>For more, check out this <a target="_blank" href="https://mobile.twitter.com/nateberkopec/status/1260602209475198976">twitter thread</a></li>
</ul>
</li>
</ul>
<p>To be honest, if I was more patient with myself, I would have gone down this path. This approach is making a comeback in light of the excess of Javascript on this modern web.</p>
<p>The bottom line is: Pick any approach you are already proficient with. But be mindful of the associated downsides, and try to mitigate them before shipping to your users.</p>
<p>With that, here is the boring stack of Sametable:</p>
<h3 id="heading-front-end">Front-end</h3>
<ul>
<li>Webpack, Babel</li>
<li><strong>Preact</strong></li>
</ul>
<h3 id="heading-back-end">Back-end</h3>
<ul>
<li><strong>Node</strong> — API server with ExpressJS</li>
<li><strong>Postgresql</strong> — Database</li>
<li><strong>Redis</strong> — Store users' session data and cache queries' results.</li>
</ul>
<h3 id="heading-hosting">Hosting</h3>
<ul>
<li><strong>Google Cloud Platform</strong> — GAE for hosting Nodejs, GCE for hosting Redis.</li>
<li><strong>Firebase</strong> — For hosting my SPA.</li>
</ul>
<p><img src="https://i.imgur.com/CA88ijh.png" alt="Sametable architecture" width="600" height="400" loading="lazy"></p>
<h2 id="heading-repo">Repo</h2>
<p><a target="_blank" href="https://github.com/kilgarenone/boileroom">https://github.com/kilgarenone/boileroom</a></p>
<p>This repo contains the structure I'm using to develop my SaaS. I have one folder for the <strong>client</strong> stuff, and another for the <strong>server</strong> stuff:</p>
<pre><code class="lang-json">- client
    - src
      - components
      - index.html
      - index.js
    - package.json
    - webpack.config.js
    -.env
    -.env.development
- server
    - server.js
    - package.json
    - .env
- package.json
- .gitignore
- .eslintrc.js
- .prettierrc.js
- .stylelintrc.js
</code></pre>
<p>The file structure always aims to be flat, cohesive, and as linear to navigate as possible. Each 'component' is self-contained within a folder with all its constituent files (html|css|js). For example, in a 'Login' route folder:</p>
<pre><code class="lang-xml">- client
   - src
     - routes
       - Login
         - Login.js
         - Login.scss
         - Login.redux.js
</code></pre>
<p>I learned this from the <a target="_blank" href="https://angular.io/guide/styleguide#angular-coding-style-guide">Angular2 style guide</a> which has a lot of other good stuff you can take away. Highly recommended.</p>
<h3 id="heading-start-full-stack-development-locally">Start Full-stack Development Locally</h3>
<p>The <code>package.json</code> at the root has a <strong>npm script</strong> that I will run to boot up <strong>both</strong> my client and server to begin my local development:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"client"</span>: <span class="hljs-string">"cd client &amp;&amp; npm run dev"</span>,
    <span class="hljs-attr">"server"</span>: <span class="hljs-string">"cd server &amp;&amp; npm run dev"</span>,
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"npm-run-all --parallel server client"</span>
}
</code></pre>
<p>Run the following in a terminal at your project's root:</p>
<pre><code class="lang-json">npm run dev
</code></pre>
<h2 id="heading-client">Client</h2>
<pre><code class="lang-json">- client
    - src
      - components
      - index.html
      - index.js
    - package.json
    - webpack.config.js
    -.env
    -.env.development
</code></pre>
<p>The file structure of the 'client' is quite like that of the 'create-react-app'. The meat of your application code is inside the <code>src</code> folder in which there is a <code>components</code> folder for your functional React components; <code>index.html</code> is your <a target="_blank" href="https://github.com/kilgarenone/boileroom/blob/master/client/config/webpack.development.js#L41-L43">custom template</a> provided to the <a target="_blank" href="https://github.com/jantimon/html-webpack-plugin#options"><code>html-webpack-plugin</code></a>; <a target="_blank" href="https://github.com/kilgarenone/boileroom/blob/master/client/src/index.js"><code>index.js</code></a> is a file as a <a target="_blank" href="https://github.com/kilgarenone/boileroom/blob/master/client/config/webpack.common.js#L12-L15">entry point</a> to Webpack.</p>
<aside>
<p><strong>Note:</strong> I have since restructured my build environment to achieve <a href="#differential-serving">differential serving</a>. Webpack and babel have been organized differently, and npm scripts changed a bit. Everything else remains the same.</p>
</aside>

<h3 id="heading-npm-scriptclient">Npm Script(Client)</h3>
<p>The client's <code>package.json</code> file has two most important npm scripts: 1) <code>dev</code> to start development, 2) <code>build</code> to bundle for production.</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"cross-env NODE_ENV=development webpack-dev-server"</span>,
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"cross-env NODE_ENV=production node_modules/.bin/webpack"</span>
}
</code></pre>
<h3 id="heading-environment-variables">Environment Variables</h3>
<p>It's a good practice to have a <code>.env</code> file where you define your sensitive values such as API keys and database credentials:</p>
<pre><code class="lang-json">SQL_PASSWORD=admin
STRIPE_API_KEY=<span class="hljs-number">1234567890</span>
</code></pre>
<p>A library called <a target="_blank" href="https://www.npmjs.com/package/dotenv">dotenv</a> is usually used to load these variables into our application code for consumption. However, in the context of Webpack, we will use <a target="_blank" href="https://www.npmjs.com/package/dotenv-webpack">dotenv_webpack</a> to do that during compile and build time <a target="_blank" href="https://github.com/kilgarenone/boileroom/blob/master/config/webpack.common.js#L33-L37">as shown here</a>. The variables will then be accessible in the <code>process.env</code> object in your codebase:</p>
<pre><code class="lang-js"><span class="hljs-comment">// payment.jsx</span>

<span class="hljs-keyword">if</span> (process.env.STRIPE_API_KEY) {
  <span class="hljs-comment">// do stuff</span>
}
</code></pre>
<h3 id="heading-webpack-amp-babel">Webpack &amp; Babel</h3>
<p>Webpack is used to lump all my UI components and its dependencies (npm libraries, files like images, fonts, SVG) into appropriate files like <code>.js</code>, <code>.css</code>, <code>.png</code> files. During the bundling, Webpack will run through my <a target="_blank" href="https://github.com/kilgarenone/boileroom/blob/master/client/config/webpack.production.js#L19-L57">babel config</a>, and, if necessary, transpiles the Javascript I have written to an older version(e.g. es5) to support my <a target="_blank" href="https://github.com/kilgarenone/boileroom/blob/master/client/package.json#L13-L27">targeted browsers</a>.</p>
<p>When Webpack has done its job, it will have generated one (or <a target="_blank" href="https://webpack.js.org/concepts/entry-points/#multi-page-application">several</a>) <code>.js</code> and <code>.css</code> files. Then by <a target="_blank" href="https://github.com/kilgarenone/boileroom/blob/master/client/config/webpack.production.js#L189-L202">using</a> a webpack plugin called <a target="_blank" href="https://github.com/jantimon/html-webpack-plugin">'html-webpack-plugin'</a>, references to those JS and CSS files are automatically (default behaviour) injected respectively as <code>&lt;script&gt;</code> and <code>&lt;link</code> in your <code>index.html</code>. Then when a user requests your app in a browser, the 'index.html' is fetched and parsed. When it sees <code>&lt;script&gt;</code> and <code>&lt;link&gt;</code>, it will fetch and execute the referenced assets, and finally your app is <a target="_blank" href="https://preactjs.com/guide/v10/api-reference/#render">rendered</a>(i.e. client-side rendering) in all its glories to the user.</p>
<p>If you are new to Webpack/Babel, I'd suggest learning them from their first principles to slowly build up your configuration instead of copy/pasting bits from the web. Nothing wrong with that, but I find it makes more sense doing it once I have the mental models of how things work.</p>
<p>I wrote about the basics here:</p>
<ul>
<li><strong><a target="_blank" href="https://medium.com/@kilgarenone/minimal-webpack-setup-a5f32c5f8960">Webpack</a></strong></li>
</ul>
<p>Once I understood the basics, I started <a target="_blank" href="https://github.com/nystudio107/annotated-webpack-4-config">referring to this resource</a> for more advanced configuration.</p>
<ul>
<li><strong><a target="_blank" href="https://medium.com/@kilgarenone/minimal-babel-setup-b12b563ee2ca">Babel</a></strong></li>
</ul>
<h3 id="heading-web-performance">Web Performance</h3>
<p>To put it simply, a web app that performs well is good for your <a target="_blank" href="https://developers.google.com/web/fundamentals/performance/why-performance-matters">users and business</a>.</p>
<p>Although web perf is a huge subject that's <a target="_blank" href="https://web.dev/fast/">well documented</a>, I would like to talk about few of the most impactful things I do for web perf (apart from <a target="_blank" href="https://images.guide/">optimizing the images</a> which can account for over 50% of a page's weight).</p>
<h4 id="heading-critical-rendering-path">Critical rendering path</h4>
<p>The goal of optimizing for the 'critical rendering path' in your page is to have it rendered and be interactive the soonest possible to your users. Let's do that.</p>
<p>We mentioned before that 'html-webpack-plugin' automatically injects references of all Webpack-generated <code>.js</code> and <code>.css</code> files for us in our <code>index.html</code>. But we don't want to do that now to have full control over their placement and applying the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content">resource hints</a>, both of which are a factor in how efficient a browser discovers and downloads them as chronicled <a target="_blank" href="https://timkadlec.com/remembers/2020-02-13-when-css-blocks/">in this article</a>.</p>
<p>Now, there are Webpack <a target="_blank" href="https://github.com/jantimon/html-webpack-plugin#plugins">plugins</a> that seem to help us in this respect, but:</p>
<ul>
<li>There was no intuitive way to control the ordering of my <code>&lt;script</code>. Well, there is <a target="_blank" href="https://github.com/jantimon/html-webpack-plugin/issues/140#issuecomment-376316414">this method</a>, but how about ordering among my <code>&lt;link&gt;</code> too?</li>
<li>There was no plugin that <code>preload</code> my CSS the way I wanted as we will see later. Well, there is <a target="_blank" href="https://github.com/GoogleChrome/preload-webpack-plugin">this</a> (no control over attributes), <a target="_blank" href="https://github.com/jantimon/resource-hints-webpack-plugin">this</a> (same), and <a target="_blank" href="https://github.com/numical/style-ext-html-webpack-plugin">this</a> (no clear support for MiniCssExtractPlugin).</li>
</ul>
<p>Even if I could've somehow hack them all together, I would have decided against it in a heartbeat if I'd known I could do it in an intuitive and controlled way. And I did.</p>
<p>So go ahead and disable the auto-injection:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// webpack.production.js</span>
<span class="hljs-attr">plugins</span>: [
  <span class="hljs-keyword">new</span> HtmlWebpackPlugin({
    <span class="hljs-attr">template</span>: settings.templatePath,
    <span class="hljs-attr">filename</span>: <span class="hljs-string">"index.html"</span>,
    <span class="hljs-attr">inject</span>: <span class="hljs-literal">false</span>, <span class="hljs-comment">// we will inject ourselves</span>
    <span class="hljs-attr">mode</span>: process.env.NODE_ENV,
  }),
];
</code></pre>
<p>And knowing that we can grab Webpack-generated assets from the <a target="_blank" href="https://github.com/jantimon/html-webpack-plugin#writing-your-own-templates"><code>htmlWebpackPlugin.files</code></a> object inside our <code>index.html</code>:</p>
<pre><code class="lang-json"><span class="hljs-comment">// example of what you would see if you</span>
<span class="hljs-comment">// console.log(htmlWebpackPlugin.files)</span>

{
  <span class="hljs-attr">"publicPath"</span>: <span class="hljs-string">"/"</span>,
  <span class="hljs-attr">"js"</span>: [
    <span class="hljs-string">"/js/runtime.a201e1a.js"</span>,
    <span class="hljs-string">"/vendors~app.d8e8c.js"</span>,
    <span class="hljs-string">"/app.f8fb511.js"</span>,
    <span class="hljs-string">"/components.3811eb.js"</span>
  ],
  <span class="hljs-attr">"css"</span>: [<span class="hljs-string">"/app.5597.css"</span>, <span class="hljs-string">"/components.b49d382.css"</span>]
}
</code></pre>
<p>We inject our assets in <code>index.html</code> ourselves:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">%</span> <span class="hljs-attr">if</span> (<span class="hljs-attr">htmlWebpackPlugin.options.mode</span> === <span class="hljs-string">'production'</span>) { %&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>
  <span class="hljs-attr">defer</span>
  <span class="hljs-attr">src</span>=<span class="hljs-string">"&lt;%= htmlWebpackPlugin.files.js.filter(e =&gt; /^\/vendors/.test(e))[0] %&gt;"</span>
&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span>
  <span class="hljs-attr">defer</span>
  <span class="hljs-attr">src</span>=<span class="hljs-string">"&lt;%= htmlWebpackPlugin.files.js.filter(e =&gt; /^\/app/.test(e))[0] %&gt;"</span>
&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span>
  <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>
  <span class="hljs-attr">href</span>=<span class="hljs-string">"&lt;%= htmlWebpackPlugin.files.css.filter(e =&gt; /app/.test(e))[0] %&gt;"</span>
/&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">%</span> } %&gt;</span>
</code></pre>
<p>Note:</p>
<ul>
<li>We only do this when building for production; we let <code>webpack-dev-server</code> injects for us during local development.</li>
<li><p>We apply the <code>defer</code> attribute on our <code>&lt;script&gt;</code> so that browser will fetch them <em>while</em> parsing our HTML, and only execute the JS once the HTML has been parsed.</p>
<figure>
<img src="https://i.imgur.com/cF7jPjB.png" alt="defer diagram" width="600" height="400" loading="lazy">
<figcaption><a href="https://hacks.mozilla.org/2017/09/building-the-dom-faster-speculative-parsing-async-defer-and-preload/">source</a></figcaption>
</figure>

</li>
</ul>
<h4 id="heading-inlining-css-and-js">Inlining CSS and JS</h4>
<p>If you <a target="_blank" href="https://web.dev/extract-critical-css/#overview-of-tools">managed</a> to separate your <em>critical</em> CSS or you have a tiny JS script, you might want to consider inlining them in <code>&lt;style&gt;</code> and <code>&lt;script&gt;</code>. </p>
<p>'Inlining' means placing corresponding raw content in HTML. This saves network trips, although not being able to cache them is a concern worth factoring in.</p>
<p>Let's inline the <code>runtime.js</code> generated by Webpack as suggested <a target="_blank" href="https://developers.google.com/web/fundamentals/performance/webpack/use-long-term-caching#inline_webpack_runtime_to_save_an_extra_http_request">here</a>. Back in the <code>index.html</code> above, add this snippet:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- more &lt;link&gt; and &lt;script&gt; --&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  &lt;%= compilation.assets[htmlWebpackPlugin.files.js.filter(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> <span class="hljs-regexp">/runtime/</span>.test(e))[<span class="hljs-number">0</span>].substr(htmlWebpackPlugin.files.publicPath.length)].source() %&gt;
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>The key was the <code>compilation.assets[&lt;ASSET_FILE_NAME&gt;].source()</code>:</p>
<blockquote>
<ul>
<li>compilation: the webpack <a target="_blank" href="https://webpack.js.org/api/compilation-object/">compilation object</a>. This can be used, for example, to get the contents of processed assets and inline them directly in the page, through <code>compilation.assets[...].source()</code> (see <a target="_blank" href="https://github.com/jantimon/html-webpack-plugin/blob/master/examples/inline/template.pug">the inline template example</a>). (<a target="_blank" href="https://github.com/jantimon/html-webpack-plugin#writing-your-own-templates">source</a>)</li>
</ul>
</blockquote>
<p>You can use this method to inline your critical CSS too:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">compilation.assets</span>[<span class="hljs-attr">htmlwebpackplugin.files.css.filter</span>(<span class="hljs-attr">e</span> =&gt;</span> /app/.test(e)) [0].substr(htmlWebpackPlugin.files.publicPath.length) ].source() %&gt;
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
</code></pre>
<p>For non-critical CSS, you can consider 'preload' them.</p>
<h4 id="heading-preload-non-critical-css">Preload non-critical CSS</h4>
<p>In short:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">link</span>
  <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>
  <span class="hljs-attr">href</span>=<span class="hljs-string">"/path/to/my.css"</span>
  <span class="hljs-attr">media</span>=<span class="hljs-string">"print"</span>
  <span class="hljs-attr">onload</span>=<span class="hljs-string">"this.media='all'"</span>
/&gt;</span>
</code></pre>
<p><a target="_blank" href="https://timkadlec.com/remembers/2020-02-13-when-css-blocks/">source</a></p>
<p>But let's see how to do this with Webpack.</p>
<p>So I have my non-critical CSS contained in a CSS file, which I specify as its own entry point in Webpack:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// webpack.config.js</span>
<span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">entry</span>: {
    <span class="hljs-attr">app</span>: <span class="hljs-string">"index.js"</span>,
    <span class="hljs-attr">components</span>: path.resolve(__dirname, <span class="hljs-string">"../src/css/components.scss"</span>),
  },
};
</code></pre>
<p>Finally, I inject it above my critical CSS:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- Preloading non-critical CSS --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span>
  <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>
  <span class="hljs-attr">href</span>=<span class="hljs-string">"&lt;%= htmlWebpackPlugin.files.css.filter(e =&gt; /components/.test(e))[0] %&gt;"</span>
  <span class="hljs-attr">media</span>=<span class="hljs-string">"print"</span>
  <span class="hljs-attr">onload</span>=<span class="hljs-string">"this.media='all'"</span>
/&gt;</span>

<span class="hljs-comment">&lt;!-- Inlined critical CSS --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">compilation.assets</span>[<span class="hljs-attr">htmlwebpackplugin.files.css.filter</span>(<span class="hljs-attr">e</span> =&gt;</span> /app/.test(e)) [0].substr(htmlWebpackPlugin.files.publicPath.length) ].source() %&gt;
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
</code></pre>
<p>Let's <strong>measure</strong> if, after all this, we have actually done anything good. Measuring the Sametable's <a target="_blank" href="https://web.sametable.app/signup">signup page</a>:</p>
<p><strong>BEFORE</strong>
<img src="https://i.imgur.com/rfy7og8.png" width="600" height="400" alt="rfy7og8" loading="lazy"></p>
<p><strong>AFTER</strong>
<img src="https://i.imgur.com/n5llJWx.png" width="600" height="400" alt="n5llJWx" loading="lazy"></p>
<p>Looks like we have improved almost all of the important user-centric metrics (not sure about the First Input Delay..)! ?</p>
<p>Here is a <a target="_blank" href="https://www.youtube.com/watch?v=j9LW94EN7n4">good video tutorial</a> about measuring web performance in the Chrome Dev tool.</p>
<h4 id="heading-code-splitting">Code splitting</h4>
<p>Rather than lump all your app's components, routes, and third-party libraries into a single <code>.js</code> file, you should split and load them on-demand based on a user's action at runtime. </p>
<p>This will <strong>dramatically</strong> reduce the bundle size of your SPA and reduces initial Javascript processing costs. This improves metrics like 'First interactive time' and 'First meaningful paint'.</p>
<p>Code splitting is done with the <a target="_blank" href="https://webpack.js.org/guides/code-splitting/#dynamic-imports">'dynamic imports'</a>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Editor.jsx</span>

<span class="hljs-comment">// LAZY-LOAD A GIGANTIC THIRD-PARTY LIBRARY</span>
componentDidMount() {
  <span class="hljs-keyword">const</span> { <span class="hljs-attr">default</span>: MarkdownIt } = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(
    <span class="hljs-comment">/* webpackChunkName: "markdown-it" */</span>
    <span class="hljs-string">"markdown-it"</span>
  );
  <span class="hljs-keyword">new</span> MarkdownIt({ <span class="hljs-attr">html</span>: <span class="hljs-literal">true</span> }).render(<span class="hljs-comment">/* stuff */</span>);
}

<span class="hljs-comment">// OR LAZY-LOAD A COMPONENT BASED ON USER ACTION</span>
checkout = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> { <span class="hljs-attr">default</span>: CheckoutModal } = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(
    <span class="hljs-comment">/* webpackChunkName: "checkoutModal" */</span>
    <span class="hljs-string">"../routes/CheckoutModal"</span>
  );
}
</code></pre>
<p>Another use case for code splitting is to <strong>conditionally load polyfill</strong> for a Web API in a browser that doesn't support it. This spares others that do support it from paying the cost of the polyfill.</p>
<p>For example, if <code>IntersectionObserver</code> isn't supported, we will polyfill it with the <a target="_blank" href="https://www.npmjs.com/package/intersection-observer">'intersection-observer'</a> library:</p>
<pre><code class="lang-js"><span class="hljs-comment">// InfiniteScroll.jsx</span>

componentDidMount() {
  (<span class="hljs-built_in">window</span>.IntersectionObserver ? <span class="hljs-built_in">Promise</span>.resolve() : <span class="hljs-keyword">import</span>(<span class="hljs-string">"intersection-observer"</span>)).then(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">this</span>.io = <span class="hljs-keyword">new</span> <span class="hljs-built_in">window</span>.IntersectionObserver(<span class="hljs-function">(<span class="hljs-params">entries</span>) =&gt;</span> {
      entries.forEach(<span class="hljs-function">(<span class="hljs-params">entry</span>) =&gt;</span> {
        <span class="hljs-comment">// do stuff</span>
      });
    }, { <span class="hljs-attr">threshold</span>: <span class="hljs-number">0.5</span> });

    <span class="hljs-built_in">this</span>.io.observe(<span class="hljs-comment">/* DOM element */</span>);
  });
}
</code></pre>
<h5 id="heading-guide">Guide</h5>
<ul>
<li><a target="_blank" href="https://medium.com/@kilgarenone/pragmatic-code-splitting-with-preact-and-webpack-a3d3b19f86a3">https://medium.com/@kilgarenone/pragmatic-code-splitting-with-preact-and-webpack-a3d3b19f86a3</a></li>
</ul>
<h3 id="heading-differential-serving">Differential Serving</h3>
<p>You have probably configured your Webpack to build your app targeting both modern and legacy browsers like IE11, while serving every user with the same payload. This forces those users who are on modern browsers to pay the cost (parse/compile/execute) of unnecessary polyfills and extraneous transformed codes that are meant to support users on legacy browsers.</p>
<p>'Differential serving' will serve, on one hand, much leaner code to users on modern browsers. And on the other hand, it'll serve properly polyfilled and transformed code to support users on legacy browsers such as IE11.</p>
<p>Although this approach makes for an even more complex build setup and doesn't come without a <a target="_blank" href="https://philipwalton.com/articles/deploying-es2015-code-in-production-today/#double-download-issue">few caveats</a>, the benefits gained (you can find in the resources below) certainly <em>outweigh</em> the costs. That is unless the majority of your user base is on IE11. In that case, you can probably skip this. But even so, this approach is future-proof as legacy browsers are being phased out.</p>
<h4 id="heading-repo-1">Repo</h4>
<p><a target="_blank" href="https://github.com/kilgarenone/differential-serving">https://github.com/kilgarenone/differential-serving</a></p>
<h4 id="heading-resources">Resources</h4>
<ul>
<li><a target="_blank" href="https://jasonformat.com/modern-script-loading/#option1loaddynamically">https://jasonformat.com/modern-script-loading/#option1loaddynamically</a> — A very good overview of different approaches to differential serving. Sametable is on the 'Option-1'.</li>
<li><a target="_blank" href="https://github.com/firsttris/html-webpack-multi-build-plugin">https://github.com/firsttris/html-webpack-multi-build-plugin</a> — This Webpack plugin passes the manifest(i.e. assets' reference) of your modern &amp; legacy scripts to 'html-webpack-plugin' so you can access them in your 'index.html'.</li>
<li><a target="_blank" href="https://calendar.perfplanet.com/2018/doing-differential-serving-in-2019/">https://calendar.perfplanet.com/2018/doing-differential-serving-in-2019/</a> — I learned here about structuring my babel config with its 'babel.config.js' method.</li>
<li><a target="_blank" href="https://github.com/nystudio107/annotated-webpack-4-config">https://github.com/nystudio107/annotated-webpack-4-config</a> — I learned a lot here about structuring my Webpack configs.</li>
</ul>
<h3 id="heading-fonts">Fonts</h3>
<p>Font files can be costly. Take my favorite font <a target="_blank" href="https://rsms.me/inter/">Inter</a> for example: If I used 3 of its font styles, the total size could get up to 300KB, exacerbating the FOUT and FOIT situations, particularly in low-end devices.</p>
<p>To meet my font needs in my projects, I usually just go with the 'system fonts' that come with the machines:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">font-family</span>: -apple-system, BlinkMacSystemFont, <span class="hljs-string">"Segoe UI"</span>, Roboto,
    Oxygen-Sans, Ubuntu, Cantarell, <span class="hljs-string">"Helvetica Neue"</span>, sans-serif;
}

<span class="hljs-selector-tag">code</span> {
  <span class="hljs-attribute">font-family</span>: SFMono-Regular, Menlo, Monaco, Consolas, <span class="hljs-string">"Liberation Mono"</span>,
    <span class="hljs-string">"Courier New"</span>;
}
</code></pre>
<p>But if you must use custom web fonts, consider doing it right:</p>
<ul>
<li>You should <a target="_blank" href="https://kevq.uk/how-to-self-host-your-web-fonts/">host</a> them <a target="_blank" href="https://www.tunetheweb.com/blog/should-you-self-host-google-fonts/">yourself</a>.</li>
<li><a target="_blank" href="https://medium.com/@kilgarenone/subsetting-your-fonts-in-windows-10-using-wsl-bae4fafa35fc">'Font-subsetting'</a> to dramatically reduce the size of the font file.</li>
<li>Go through this <a target="_blank" href="https://www.zachleat.com/web/font-checklist/">checklist</a>.</li>
</ul>
<h3 id="heading-icons">Icons</h3>
<p>Icons in Sametable are SVG. There are different ways that you can do it:</p>
<ul>
<li>Copy and paste the markup of an SVG icon wherever you need it. The downside is it will bloat the HTML and incur parsing costs particularly on mobile.</li>
<li>Request for your SVG icons over the network:<code>&lt;img src="./tick.svg" /&gt;</code>. Unless an SVG is huge (&gt; 5KB), making a request for every one of them seems a bit much.</li>
<li>Make an icon reusable in the form of a <a target="_blank" href="https://medium.com/@david.gilbertson/icons-as-react-components-de3e33cb8792">React component</a>. The downside is it unnecessarily introduces Javascript and its associated costs.</li>
</ul>
<p>Instead, the solution I opted for my icons was '<strong>SVG sprites</strong>' which is closer to the nature of SVG itself ( <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use"><code>&lt;use&gt;</code></a> and <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/symbol"><code>&lt;symbol&gt;</code></a>).</p>
<p>Let's see how.</p>
<p>Say there are many places that will use two of our SVG icons. In your <code>index.html</code>:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"display: none;"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">symbol</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"pin-it"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 96 96"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Give it a title<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">desc</span>&gt;</span>Give it a description for accessibility<span class="hljs-tag">&lt;/<span class="hljs-name">desc</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">path</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M67.7 40.3c-.3 2.7-2"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">symbol</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">symbol</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"unpin-it"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 96 96"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Un-pin this entity<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">desc</span>&gt;</span>Click to un-pin this entity<span class="hljs-tag">&lt;/<span class="hljs-name">desc</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">path</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M67.7 40.3c-.3 2.7-2"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">symbol</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre>
<ol>
<li>Hide the parent SVG element <code>style="display: none"</code>.</li>
<li>Give each SVG symbol an unique id <code>&lt;symbol id="unique-id"</code>.</li>
<li>Make sure to define the <code>viewBox</code>(usually already provided), but skip the <code>width</code> and <code>height</code>.</li>
<li>Give it <code>title</code> and <code>desc</code> for accessibility.</li>
<li>And of course, the <code>path</code> data of an icon.</li>
</ol>
<p>And finally, here is how you can use them in your components:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// example.jsx</span>

render() {
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">svg</span>
    <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span>
    <span class="hljs-attr">xmlnsXlink</span>=<span class="hljs-string">"http://www.w3.org/1999/xlink"</span>
    <span class="hljs-attr">width</span>=<span class="hljs-string">"24"</span>
    <span class="hljs-attr">height</span>=<span class="hljs-string">"24"</span>
  &gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">use</span> <span class="hljs-attr">xlinkHref</span>=<span class="hljs-string">"#pin-it"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span></span>

}
</code></pre>
<ol>
<li>Define the <code>width</code> and <code>height</code> as desired.</li>
<li>Specify the <code>id</code> of the <code>&lt;symbol&gt;</code>: <code>&lt;use xlinkHref="#pin-it" /&gt;</code>.</li>
</ol>
<h4 id="heading-lazy-load-svg-sprites">Lazy load SVG sprites</h4>
<p>Rather than having your SVG symbols in the <code>index.html</code>, you can put them in a <code>.svg</code> file which is loaded only when needed:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">symbol</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"header-1"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 26 24"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Header 1<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">desc</span>&gt;</span>Toggle a h1 header<span class="hljs-tag">&lt;/<span class="hljs-name">desc</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">text</span> <span class="hljs-attr">x</span>=<span class="hljs-string">"0"</span> <span class="hljs-attr">y</span>=<span class="hljs-string">"20"</span> <span class="hljs-attr">font-weight</span>=<span class="hljs-string">"600"</span>&gt;</span>H1<span class="hljs-tag">&lt;/<span class="hljs-name">text</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">symbol</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">symbol</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"header-2"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 26 24"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Header 2<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">desc</span>&gt;</span>Toggle a h2 header<span class="hljs-tag">&lt;/<span class="hljs-name">desc</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">text</span> <span class="hljs-attr">x</span>=<span class="hljs-string">"0"</span> <span class="hljs-attr">y</span>=<span class="hljs-string">"20"</span> <span class="hljs-attr">font-weight</span>=<span class="hljs-string">"600"</span>&gt;</span>H2<span class="hljs-tag">&lt;/<span class="hljs-name">text</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">symbol</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
</code></pre>
<p>Put that file in <code>client/src/assets</code>:</p>
<pre><code class="lang-xml">- client
  - src
    - assets
      - svg-sprites.svg
</code></pre>
<p>Finally, to use one of the symbols in the file:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Editor.js</span>

<span class="hljs-keyword">import</span> svgSprites <span class="hljs-keyword">from</span> <span class="hljs-string">"../../assets/svg-sprites.svg"</span>;

<span class="hljs-comment">/* component stuff */</span>

render() {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">svg</span>
        <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span>
        <span class="hljs-attr">xmlnsXlink</span>=<span class="hljs-string">"http://www.w3.org/1999/xlink"</span>
        <span class="hljs-attr">width</span>=<span class="hljs-string">"24"</span>
        <span class="hljs-attr">height</span>=<span class="hljs-string">"24"</span>
      &gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">use</span> <span class="hljs-attr">xlinkHref</span>=<span class="hljs-string">{</span>`${<span class="hljs-attr">svgSprites</span>}#<span class="hljs-attr">header-1</span>`} /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span></span>
  )
}
</code></pre>
<p>And a browser will, during runtime, fetch the <code>.svg</code> file if it hasn't already.</p>
<p>And there you have it! No more plastering those lengthy <code>path</code> data all over the place.</p>
<h4 id="heading-sources-of-icons">Sources of icons</h4>
<ul>
<li><a target="_blank" href="https://material.io/resources/icons/?style=baseline">https://material.io/resources/icons/?style=baseline</a></li>
<li><a target="_blank" href="https://logomakr.com/">https://logomakr.com/</a></li>
<li><a target="_blank" href="https://github.com/wmira/react-icons-kit#bundled-icon-sets">https://github.com/wmira/react-icons-kit#bundled-icon-sets</a> (has a nice list of sources)</li>
</ul>
<h4 id="heading-references">References</h4>
<ul>
<li><a target="_blank" href="https://css-tricks.com/mega-list-svg-information/#svg-icons">https://css-tricks.com/mega-list-svg-information/#svg-icons</a></li>
</ul>
<h3 id="heading-favicon">Favicon</h3>
<p>If I hadn't disabled the <code>inject</code> option of 'html-webpack-plugin', I would have used a plugin called <a target="_blank" href="https://github.com/jantimon/favicons-webpack-plugin">'favicons-webpack-plugin'</a> that automatically generates all types of favicons (beware - it's a lot!), and injects them in my <code>index.html</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// webpack.config.js</span>

<span class="hljs-attr">plugins</span>: [
  <span class="hljs-keyword">new</span> HtmlWebpackPlugin(), <span class="hljs-comment">// 'inject' is true by default</span>
  <span class="hljs-comment">// must come after html-webpack-plugin</span>
  <span class="hljs-keyword">new</span> FaviconsWebpackPlugin({
    <span class="hljs-attr">logo</span>: path.resolve(__dirname, <span class="hljs-string">"../src/assets/logo.svg"</span>),
    <span class="hljs-attr">prefix</span>: <span class="hljs-string">"icons-[hash]/"</span>,
    <span class="hljs-attr">persistentCache</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">inject</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">favicons</span>: {
      <span class="hljs-attr">appName</span>: <span class="hljs-string">"Sametable"</span>,
      <span class="hljs-attr">appDescription</span>: <span class="hljs-string">"Manage your tasks in spreadsheets"</span>,
      <span class="hljs-attr">developerName</span>: <span class="hljs-string">"Kheoh Yee Wei"</span>,
      <span class="hljs-attr">developerURL</span>: <span class="hljs-string">"https://kheohyeewei.com"</span>, <span class="hljs-comment">// prevent retrieving from the nearest package.json</span>
      <span class="hljs-attr">theme_color</span>: <span class="hljs-string">"#fcbdaa"</span>,
      <span class="hljs-comment">// specify the vendors that you want favicon for</span>
      <span class="hljs-attr">icons</span>: {
        <span class="hljs-attr">coast</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-attr">yandex</span>: <span class="hljs-literal">false</span>,
      },
    },
  }),
];
</code></pre>
<p>But since I have disabled the auto-injection, here is how I handle my favicon:</p>
<ol>
<li><p>Go to <a target="_blank" href="https://realfavicongenerator.net/">https://realfavicongenerator.net/</a></p>
<ul>
<li>Provide your logo in SVG format.</li>
<li>Select the 'Version/Refresh' option to enable cache-busting your favicon asset in your users' browser.</li>
<li>Complete the instructions at the end. You can store your favicons in any folder in your project.</li>
</ul>
</li>
<li><p>Use <a target="_blank" href="https://webpack.js.org/plugins/copy-webpack-plugin/">'copy-webpack-plugin'</a> to copy all your favicon assets generated from Step-1, from the folder where you store them (in my case, <code>src/assets/favicon</code>) to Webpack's output's <a target="_blank" href="https://github.com/kilgarenone/boileroom/blob/master/client/config/webpack.production.js#L140">path</a> (<a target="_blank" href="https://github.com/webpack-contrib/copy-webpack-plugin#to">default behaviour</a>), so that they will be accessible from the root (i.e. https://example.com/favicon.ico).</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// webpack.config.js</span>
<span class="hljs-keyword">const</span> CopyWebpackPlugin = <span class="hljs-built_in">require</span>(<span class="hljs-string">"copy-webpack-plugin"</span>);

plugins: [<span class="hljs-keyword">new</span> CopyWebpackPlugin([{ <span class="hljs-attr">from</span>: <span class="hljs-string">"src/assets/favicon"</span> }])];
</code></pre>
</li>
</ol>
<p>And that's it!</p>
<h3 id="heading-api-calls">API Calls</h3>
<p>A client needs to communicate with a server to perform 'CRUD' operations - Create, Read, Update, and Delete:</p>
<p><img src="https://i.imgur.com/VjAWItp.png" alt="client and server communication diagram" width="600" height="400" loading="lazy"></p>
<p>Here is my hopefully easy to understand <code>api.js</code>:</p>
<details>
  <summary>API WRAPPER</summary>

<code>javascript
import { route } from "preact-router";

function checkStatus(response) {
  const responseCode = response.status;

  if (responseCode &gt;= 200 &amp;&amp; responseCode &lt; 300) {
    return response;
  }

  // handle user not authorized scenario
  if (responseCode === 401) {
    response
      .json()
      .then((json) =&gt;
        route(`/signin${json.refererUri ? `?dest=${json.refererUri}` : ""}`)
      );
    return;
  }

  // pass along error response to the 'catch' block of your await/async try &amp; catch block
  return response.json().then((json) =&gt; {
    return Promise.reject({
      status: responseCode,
      ok: false,
      statusText: response.statusText,
      body: json,
    });
  });
}

function handleError(error) {
  error.response = {
    status: 0,
    statusText:
      "Cannot connect. Please make sure you are connected to internet.",
  };
  throw error;
}

function parseJSON(response) {
  if (response.status === 204 || response.status === 205) {
    return null;
  }
  return response.json();
}

function request(url, options) {
  return fetch(url, options)
    .catch(handleError) // handle network issues
    .then(checkStatus)
    .then(parseJSON)
    .catch((e) =&gt; {
      throw e;
    });
}

export function api(endPoint, userOptions = {}) {
  const url = process.env.API_BASE_URL + endPoint;

  // to pass along our auth cookie to server
  userOptions.credentials = "include";

  const defaultHeaders = {
    "Content-Type": "application/json",
    Accept: "application/json",
  };

  if (userOptions.body instanceof File) {
    const formData = new FormData();
    formData.append("file", userOptions.body);
    userOptions.body = formData;
    // let browser set content-type to multipart/etc.
    delete defaultHeaders["Content-Type"];
  }

  if (userOptions.body instanceof FormData) {
    // let browser set content-type to multipart
    delete defaultHeaders["Content-Type"];
  }

  const options = {
    ...userOptions,
    headers: {
      ...defaultHeaders,
      ...userOptions.headers,
    },
  };

  return request(url, options);
}</code>

</details>

<p>There is almost nothing new to learn to start using this API module if you have used the native <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch"><code>fetch</code></a> before.</p>
<h4 id="heading-usage">Usage</h4>
<pre><code class="lang-javascript"><span class="hljs-comment">// Home.jsx</span>
<span class="hljs-keyword">import</span> { api } <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/api"</span>;

<span class="hljs-keyword">async</span> componentDidMount() {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// POST-ing data</span>
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> api(
      <span class="hljs-string">'/projects/save/121212121'</span>,
      {
        <span class="hljs-attr">method</span>: <span class="hljs-string">'PUT'</span>,
        <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(dataObject)
      }
    )

    <span class="hljs-comment">// or GET-ting data</span>
    <span class="hljs-keyword">const</span> { myWorkspaces } = <span class="hljs-keyword">await</span> api(<span class="hljs-string">'/users/home'</span>);

  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-comment">// handle Promise.reject passed from api.js</span>
  }
}
</code></pre>
<p>But if you prefer to use a library to handle your HTTP calls, I'd recommend <a target="_blank" href="https://github.com/developit/redaxios">'redaxios'</a>. It not only shares an API with the popular <a target="_blank" href="https://www.npmjs.com/package/axios">axios</a>, but it's much more lightweight.</p>
<h3 id="heading-test-production-build-locally">Test Production Build Locally</h3>
<p>I always build my client app locally to test and measure in my browser before I deploy to the cloud.</p>
<p>I have an npm script (<code>npm run test-build</code>) in the <code>package.json</code> of the 'client' folder that will build and serve on a local web server. This way I can play with it in my browser at http://localhost:5000:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"test-build"</span>: <span class="hljs-string">"cross-env NODE_ENV=production TEST_RUN=true node_modules/.bin/webpack &amp;&amp; npm run serve"</span>,
    <span class="hljs-attr">"serve"</span>: <span class="hljs-string">"ws --spa index.html --directory dist --port 5000 --hostname localhost"</span>
  }
</code></pre>
<p>The app is served using a tool called <a target="_blank" href="https://www.npmjs.com/package/local-web-server">'local-web-server'</a>. It's so far the only one I find works perfectly for a SPA.</p>
<h3 id="heading-security">Security</h3>
<p>Consider adding the <a target="_blank" href="https://developers.google.com/web/fundamentals/security/csp/">CSP</a> security headers.</p>
<p>To add headers in firebase: <a target="_blank" href="https://firebase.google.com/docs/hosting/full-config#headers">https://firebase.google.com/docs/hosting/full-config#headers</a></p>
<p>Sample of CSP headers in your <code>firebase.json</code>:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"source"</span>: <span class="hljs-string">"**"</span>,
  <span class="hljs-attr">"headers"</span>: [
    {
      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"Strict-Transport-Security"</span>,
      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"max-age=63072000; includeSubdomains; preload"</span>
    },
    {
      <span class="hljs-attr">"key"</span>: <span class="hljs-string">"Content-Security-Policy"</span>,
      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'"</span>
    },
    { <span class="hljs-attr">"key"</span>: <span class="hljs-string">"X-Content-Type-Options"</span>, <span class="hljs-attr">"value"</span>: <span class="hljs-string">"nosniff"</span> },
    { <span class="hljs-attr">"key"</span>: <span class="hljs-string">"X-Frame-Options"</span>, <span class="hljs-attr">"value"</span>: <span class="hljs-string">"DENY"</span> },
    { <span class="hljs-attr">"key"</span>: <span class="hljs-string">"X-XSS-Protection"</span>, <span class="hljs-attr">"value"</span>: <span class="hljs-string">"1; mode=block"</span> },
    { <span class="hljs-attr">"key"</span>: <span class="hljs-string">"Referrer-Policy"</span>, <span class="hljs-attr">"value"</span>: <span class="hljs-string">"same-origin"</span> }
  ]
}
</code></pre>
<p>If you use Stripe, make sure you add their CSP directives too:
<a target="_blank" href="https://stripe.com/docs/security/guide#content-security-policy">https://stripe.com/docs/security/guide#content-security-policy</a></p>
<p>Finally, make sure you get an <strong>A</strong> <a target="_blank" href="https://observatory.mozilla.org/">here</a> and pat yourself on the back!</p>
<h2 id="heading-design">Design</h2>
<p>Before I start to code anything up, I wanted to have a mental reel of how I would want to <strong>on-board</strong> a new user to my app. Then I would sketch on a notebook of what it might look like doing it, and re-iterate the sketches while playing and rehashing the reel in my head. </p>
<p>For my very first 'sprint', I would primarily build a 'UI/UX framework' upon which I would add pieces over time. However, it's important to remember that every decision you make during this process should be one that's open-ended and easy to undo. </p>
<p>This way a 'small'— but careful—decision won't spell doom when you get carried away with any over-confident and romantic convictions.</p>
<p>Not sure if that made any sense, but let's explore a few concepts that helped structure my design to be coherent in practise.</p>
<h3 id="heading-modular-scale">Modular Scale</h3>
<p>Your design will make more sense to your users when it flows according to a 'modular scale'. That scale should specify a scale of spaces or sizes that each increment with a certain ratio.</p>
<figure>
<img src="https://i.imgur.com/WWl6KuB.png" alt="modular scale illustration" width="600" height="400" loading="lazy">
<figcaption><em>Figure: Modular scale</em></figcaption>
</figure>

<p>One way to create a scale is with CSS <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties">'Custom Properties'</a>(credits to view-source <a target="_blank" href="https://every-layout.dev/">every-layout.dev</a>):</p>
<pre><code class="lang-css"><span class="hljs-selector-pseudo">:root</span> {
  <span class="hljs-attribute">--ratio</span>: <span class="hljs-number">1.414</span>;
  <span class="hljs-attribute">--s-3</span>: <span class="hljs-built_in">calc</span>(var(--s0) / <span class="hljs-built_in">var</span>(--ratio) / <span class="hljs-built_in">var</span>(--ratio) / <span class="hljs-built_in">var</span>(--ratio));
  <span class="hljs-attribute">--s-2</span>: <span class="hljs-built_in">calc</span>(var(--s0) / <span class="hljs-built_in">var</span>(--ratio) / <span class="hljs-built_in">var</span>(--ratio));
  <span class="hljs-attribute">--s-1</span>: <span class="hljs-built_in">calc</span>(var(--s0) / <span class="hljs-built_in">var</span>(--ratio));
  <span class="hljs-attribute">--s0</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">--s1</span>: <span class="hljs-built_in">calc</span>(var(--s0) * <span class="hljs-built_in">var</span>(--ratio));
  <span class="hljs-attribute">--s2</span>: <span class="hljs-built_in">calc</span>(var(--s0) * <span class="hljs-built_in">var</span>(--ratio) * <span class="hljs-built_in">var</span>(--ratio));
  <span class="hljs-attribute">--s3</span>: <span class="hljs-built_in">calc</span>(var(--s0) * <span class="hljs-built_in">var</span>(--ratio) * <span class="hljs-built_in">var</span>(--ratio) * <span class="hljs-built_in">var</span>(--ratio));
}
</code></pre>
<p>If you don't know what scale to use, just <a target="_blank" href="https://www.modularscale.com/">pick a scale</a> that fits closest to your design and <strong>stick to it</strong>.</p>
<p>Then create a bunch of utility classes, each associated with a scale, in a file call <code>spacing.scss</code>. I will use them to space my UI elements across a project:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.mb-1</span> {
  <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-built_in">var</span>(--s1);
}
<span class="hljs-selector-class">.mb-2</span> {
  <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-built_in">var</span>(--s2);
}
<span class="hljs-selector-class">.mr-1</span> {
  <span class="hljs-attribute">margin-right</span>: <span class="hljs-built_in">var</span>(--s1);
}
<span class="hljs-selector-class">.mr--1</span> {
  <span class="hljs-attribute">margin-right</span>: <span class="hljs-built_in">var</span>(--s-<span class="hljs-number">1</span>);
}
</code></pre>
<p>Notice that I try to define the spacing only in the <code>right</code> and <code>bottom</code> direction as <a target="_blank" href="https://csswizardry.com/2012/06/single-direction-margin-declarations/">suggested here</a>.</p>
<p>In my experience, it's better to not bake in any spacing definitions in your UI components:</p>
<p><strong>DON'T</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Button.scss</span>
.btn {
  <span class="hljs-attr">margin</span>: <span class="hljs-number">10</span>px; <span class="hljs-comment">// a default spacing; annoying to have in most cases</span>
  font-style: normal;
  border: <span class="hljs-number">0</span>;
  background-color: transparent;
}

<span class="hljs-comment">// Button.jsx</span>
<span class="hljs-keyword">import</span> s <span class="hljs-keyword">from</span> <span class="hljs-string">'./Button.scss'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Button</span>(<span class="hljs-params">{children, ...props}</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">{s.btn}</span> {<span class="hljs-attr">...props</span>}&gt;</span>{children}<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span></span>
  )
}

<span class="hljs-comment">// Usage</span>
&lt;Button /&gt;
</code></pre>
<p><strong>DO</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Button.scss</span>
.btn {
  font-style: normal;
  border: <span class="hljs-number">0</span>;
  background-color: transparent;
}

<span class="hljs-comment">// Button.jsx</span>
<span class="hljs-keyword">import</span> s <span class="hljs-keyword">from</span> <span class="hljs-string">'./Button.scss'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Button</span>(<span class="hljs-params">{children, className, ...props}</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">{</span>`${<span class="hljs-attr">s.btn</span>} ${<span class="hljs-attr">className</span>}`} {<span class="hljs-attr">...props</span>}&gt;</span>{children}<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span></span>
  )
}

<span class="hljs-comment">// Usage</span>
<span class="hljs-comment">// Pass your spacing utility classes when building your pages</span>
&lt;Button className=<span class="hljs-string">"mr-1 pb-1"</span>&gt;Sign Up&lt;/Button&gt;
</code></pre>
<h3 id="heading-colors">Colors</h3>
<p>There are many color palette tools out there. But the one from <a target="_blank" href="https://material.io/design/color/the-color-system.html#tools-for-picking-colors">Material</a> is the one I always go to for my colors simply because they are laid out in all their glory! ?</p>
<p>Then I will define them as CSS Custom Properties again:</p>
<pre><code class="lang-css"><span class="hljs-selector-pseudo">:root</span> {
  <span class="hljs-attribute">--black-100</span>: <span class="hljs-number">#0b0c0c</span>;
  <span class="hljs-attribute">--black-80</span>: <span class="hljs-number">#424242</span>;
  <span class="hljs-attribute">--black-60</span>: <span class="hljs-number">#555759</span>;
  <span class="hljs-attribute">--black-50</span>: <span class="hljs-number">#626a6e</span>;

  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">105%</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--black-<span class="hljs-number">100</span>);
}
</code></pre>
<h3 id="heading-css-reset">CSS Reset</h3>
<p>The purpose of a 'CSS reset' is to remove the default styling of common browsers.</p>
<p>There are quite a few of those out there. Beware that some can get quite opinionated and potentially give you more headaches than they're worth. Here is a popular one: <a target="_blank" href="https://meyerweb.com/eric/tools/css/reset/reset.css">https://meyerweb.com/eric/tools/css/reset/reset.css</a></p>
<p>Here is mine:</p>
<pre><code class="lang-css">*,
*<span class="hljs-selector-pseudo">::before</span>,
*<span class="hljs-selector-pseudo">::after</span> {
  <span class="hljs-attribute">box-sizing</span>: border-box;
  <span class="hljs-attribute">overflow-wrap</span>: break-word;
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">border</span>: <span class="hljs-number">0</span> solid;
  <span class="hljs-attribute">font-family</span>: inherit;
  <span class="hljs-attribute">color</span>: inherit;
}

<span class="hljs-comment">/* Set core body defaults */</span>
<span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">scroll-behavior</span>: smooth;
  <span class="hljs-attribute">text-rendering</span>: optimizeLegibility;
}

<span class="hljs-comment">/* Make images easier to work with */</span>
<span class="hljs-selector-tag">img</span> {
  <span class="hljs-attribute">max-width</span>: <span class="hljs-number">100%</span>;
}

<span class="hljs-comment">/* Inherit fonts for inputs and buttons */</span>
<span class="hljs-selector-tag">button</span>,
<span class="hljs-selector-tag">input</span>,
<span class="hljs-selector-tag">textarea</span>,
<span class="hljs-selector-tag">select</span> {
  <span class="hljs-attribute">color</span>: inherit;
  <span class="hljs-attribute">font</span>: inherit;
}
</code></pre>
<p>You could also consider using <a target="_blank" href="https://github.com/csstools/postcss-normalize">postcss-normalize</a> that generates one according to your targeted browsers.</p>
<h3 id="heading-a-styling-practice">A Styling Practice</h3>
<p>I always try to style at the <strong>tag</strong>-level first before bringing out the big gun if necessary, in my case, <a target="_blank" href="https://github.com/css-modules/css-modules">'CSS Modules'</a>, for encapsulating styles per component:</p>
<pre><code class="lang-xml">- src
  - routes
    - SignIn
      - SignIn.js
      - SignIn.scss
</code></pre>
<p>The <code>SignIn.scss</code> contains CSS that pertains only to the <code>&lt;SignIn /&gt;</code> component.</p>
<p>Furthermore, I don't use the CSS libraries popular in the React ecosystem such as 'styled-components' and 'emotion'. I try to <strong>use pure HTML and CSS whenever I can, and only let Preact handle the DOM and state updates</strong> for me.</p>
<p>For example, for the <code>&lt;input/&gt;</code> element:</p>
<pre><code class="lang-css">// <span class="hljs-selector-tag">index</span><span class="hljs-selector-class">.scss</span>

<span class="hljs-selector-tag">label</span> {
  <span class="hljs-attribute">display</span>: block;
  <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--black-<span class="hljs-number">100</span>);
  <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">600</span>;
}

<span class="hljs-selector-tag">input</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">400</span>;
  <span class="hljs-attribute">font-style</span>: normal;
  <span class="hljs-attribute">border</span>: <span class="hljs-number">2px</span> solid <span class="hljs-built_in">var</span>(--black-<span class="hljs-number">100</span>);
  <span class="hljs-attribute">box-shadow</span>: none;
  <span class="hljs-attribute">outline</span>: none;
  <span class="hljs-attribute">appearance</span>: none;
}

<span class="hljs-selector-tag">input</span><span class="hljs-selector-pseudo">:focus</span> {
  <span class="hljs-attribute">box-shadow</span>: inset <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">2px</span>;
  <span class="hljs-attribute">outline</span>: <span class="hljs-number">3px</span> solid <span class="hljs-number">#fd0</span>;
  <span class="hljs-attribute">outline-offset</span>: <span class="hljs-number">0</span>;
}
</code></pre>
<p>Then using it in a JSX file with its vanilla tag:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// SignIn.js</span>

render() {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"email"</span>&gt;</span>
        Email<span class="hljs-symbol">&amp;nbsp;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">abbr</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"This field is required"</span>&gt;</span>*<span class="hljs-tag">&lt;/<span class="hljs-name">abbr</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
        <span class="hljs-attr">required</span>
        <span class="hljs-attr">value</span>=<span class="hljs-string">{this.email}</span>
        <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span>
        <span class="hljs-attr">id</span>=<span class="hljs-string">"email"</span>
        <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span>
        <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"e.g. sara@widgetco.com"</span>
      /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}
</code></pre>
<h3 id="heading-layout">Layout</h3>
<p>I use <strong>CSS Flexbox</strong> for layout works in Sametable. I didn't need any CSS frameworks. Learn CSS Flexbox from its first principles to do more with less code. Plus, in many cases, the result will already be responsive thanks to the layout algorithms, saving those <code>@media</code> queries.</p>
<p>Let's see how to build a common layout in Flexbox with a minimal amount of CSS:</p>
<p><img src="https://i.imgur.com/PTCrd0K.png" alt="sidebar and content layout" width="600" height="400" loading="lazy"></p>
<p>
  <span>See the Pen <a href="https://codepen.io/kilgarenone/pen/mdeLwvx">
  Sidebar/Content layout</a>
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

<h4 id="heading-resources-1">Resources</h4>
<ul>
<li><a target="_blank" href="https://flexboxfroggy.com/">Flexbox froggy</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/understanding-flexbox-everything-you-need-to-know-b4013d4dc9af/">Everything you need to know about flexbox</a></li>
</ul>
<h2 id="heading-server">Server</h2>
<pre><code class="lang-json">- server
  - server.js
  - package.json
  - .env
</code></pre>
<p>The <a target="_blank" href="https://github.com/kilgarenone/boileroom/tree/master/server">server</a> is run on NodeJS(ExpressJS framework) to serve all my <strong>API</strong> endpoints.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Example endpoint: https://example.com/api/tasks/save/12345</span>
router.put(<span class="hljs-string">"/save/:taskId"</span>, <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> {});
</code></pre>
<p>The <a target="_blank" href="https://github.com/kilgarenone/boileroom/blob/master/server/server.js"><code>server.js</code></a> contains the <a target="_blank" href="https://expressjs.com/en/starter/hello-world.html">familiar</a> codes to start a Nodejs server.</p>
<h3 id="heading-file-structure">File Structure</h3>
<p>I'm grateful for this digestible <a target="_blank" href="https://node-postgres.com/guides/project-structure">guide</a> about project structure, which allowed me to hunker down and quickly build out my API.</p>
<h3 id="heading-npm-scriptserver">Npm Script(Server)</h3>
<p>In the <code>package.json</code> inside the 'server' folder, there is a npm script that will start your server for you:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
  <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"nodemon -r dotenv/config server.js"</span>,
  <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node server.js"</span>
}
</code></pre>
<ul>
<li><p>The <code>dev</code> script <a target="_blank" href="https://www.npmjs.com/package/dotenv#preload">'preload'</a> dotenv as suggested <a target="_blank" href="https://medium.com/the-node-js-collection/making-your-node-js-work-everywhere-with-environment-variables-2da8cdf6e786#b1af">here</a>. And that's it— You will have access to the env variables defined in the <code>.env</code> file from the <code>process.env</code> object.</p>
</li>
<li><p>The <code>start</code> script is used to start our Nodejs server in production. In my case, GCP will run this script to bootup my Nodejs.</p>
</li>
</ul>
<h3 id="heading-database">Database</h3>
<p>I use <strong>Postgresql</strong> as my database. Then I use the <a target="_blank" href="https://node-postgres.com/">'node-postgres'</a>(a.k.a <code>pg</code>) library to connect my Nodejs to the database. Once that's done, I can do CRUD operations between my API endpoints and the database.</p>
<h4 id="heading-setup">Setup</h4>
<p>For local development:</p>
<ol>
<li><p>Download <a target="_blank" href="https://www.enterprisedb.com/downloads/postgres-postgresql-downloads">Postgresql here</a>. Get the latest version. Leave everything as it is. Remember the password you set. Then,</p>
<ul>
<li>Open 'pgAdmin'. It's a browser application.</li>
<li>Create a database for you app:
<img src="https://i.imgur.com/trcAaSi.png" width="600" height="400" alt="trcAaSi" loading="lazy"></li>
</ul>
</li>
<li><p>Define a set of environment variables in the <code>.env</code> file:</p>
<pre><code class="lang-json">DB_HOST='localhost'
DB_USER=postgres
DB_NAME=&lt;YOUR_CUSTOM_DATABASE_NAME_HERE&gt;
DB_PASSWORD=&lt;YOUR_MASTER_PASSWORD&gt;
DB_PORT=<span class="hljs-number">5432</span>
</code></pre>
</li>
<li><p>Then we will <a target="_blank" href="https://node-postgres.com/features/connecting">connect</a> a new client through a <a target="_blank" href="https://node-postgres.com/features/pooling">connection pool</a> to our Postgresql database from our Nodejs. I do it in <code>server/db/index.js</code>:</p>
<p><a href="#db-wrapper" id="db-wrapper">#</a></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { Pool } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"pg"</span>);

<span class="hljs-keyword">const</span> pool = <span class="hljs-keyword">new</span> Pool({
  <span class="hljs-attr">user</span>: process.env.DB_USER,
  <span class="hljs-attr">host</span>: process.env.DB_HOST,
  <span class="hljs-attr">port</span>: process.env.DB_PORT,
  <span class="hljs-attr">database</span>: process.env.DB_NAME,
  <span class="hljs-attr">password</span>: process.env.DB_PASSWORD,
});

<span class="hljs-comment">// TRANSACTION</span>
<span class="hljs-comment">// https://github.com/brianc/node-postgres/issues/1252#issuecomment-293899088</span>
<span class="hljs-keyword">const</span> tx = <span class="hljs-keyword">async</span> (callback, errCallback) =&gt; {
  <span class="hljs-keyword">const</span> client = <span class="hljs-keyword">await</span> pool.connect();
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> client.query(<span class="hljs-string">"BEGIN"</span>);
    <span class="hljs-keyword">await</span> callback(client);
    <span class="hljs-keyword">await</span> client.query(<span class="hljs-string">"COMMIT"</span>);
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.log((<span class="hljs-string">"DB ERROR:"</span>, err));
    <span class="hljs-keyword">await</span> client.query(<span class="hljs-string">"ROLLBACK"</span>);
    errCallback &amp;&amp; errCallback(err);
  } <span class="hljs-keyword">finally</span> {
    client.release();
  }
};
<span class="hljs-comment">// the pool will emit an error on behalf of any idle clients</span>
<span class="hljs-comment">// it contains if a backend error or network partition happens</span>
pool.on(<span class="hljs-string">"error"</span>, <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
  process.exit(<span class="hljs-number">-1</span>);
});

pool.on(<span class="hljs-string">"connect"</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"❤️ Connected to the Database ❤️"</span>);
});

<span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">query</span>: <span class="hljs-function">(<span class="hljs-params">text, params, callback</span>) =&gt;</span> pool.query(text, params, callback),
  tx,
  pool,
};
</code></pre>
<ul>
<li>I will use the <code>tx</code> function in an API if I have to call <strong>many</strong> queries that depend on each other.</li>
<li>If I'm making a <strong>single</strong> query, I will use the <code>query</code> function.</li>
</ul>
</li>
</ol>
<p>And that's it! Now you have a database to work with for your local development ?</p>
<h4 id="heading-usage-1">Usage</h4>
<p>I will confess: I <strong>hand-crafted</strong> all the queries for Sametable.</p>
<p>In my opinion, SQL itself is already a declarative language that needs no further abstraction—it's easy to read, understand, and write. It can be maintainable if you separated well your API endpoints. </p>
<p>If you knew you were building a facebook-scale app, perhaps it would be wise to use an ORM. But I'm just a <a target="_blank" href="https://www.youtube.com/watch?v=5PsnxDQvQpw">everyday normal guy</a> building a very narrow-scoped SaaS all by myself. </p>
<p>So I needed to avoid overhead and complexity while considering factors such as ease of onboarding, performance, ease of reiteration, and the potential lifespan of the knowledge. </p>
<p>This reminds me of being urged to learn vanilla JavaScript before jumping on the bandwagon of a popular front-end framework. Because you just might realize: That's all you need for what you have set out to accomplish to reach your 1000th customer.</p>
<p>To be fair, though, when I decided to go down this path, I'd had modest experiences in writing MySQL. So if you know nothing about SQL and you are anxious to ship it, then you might want to consider a library like <a target="_blank" href="http://knexjs.org/">knex.js</a>.</p>
<h5 id="heading-example">Example</h5>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/routes/projects.js</span>

<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> asyncHandler = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express-async-handler"</span>);
<span class="hljs-keyword">const</span> db = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../db"</span>);

<span class="hljs-keyword">const</span> router = express.Router();

<span class="hljs-built_in">module</span>.exports = router;

<span class="hljs-comment">// [POST] api/projects/create</span>
router.post(
  <span class="hljs-string">"/create"</span>,
  express.json(),
  asyncHandler(<span class="hljs-keyword">async</span> (req, res, next) =&gt; {
    <span class="hljs-keyword">const</span> { title, project_id } = req.body;

    db.tx(<span class="hljs-keyword">async</span> (client) =&gt; {
      <span class="hljs-keyword">const</span> {
        rows,
      } = <span class="hljs-keyword">await</span> client.query(
        <span class="hljs-string">`INSERT INTO tasks (title) VALUES ($1) RETURNING mask_id(task_id) as masked_task_id, task_id`</span>,
        [title]
      );

      res.json({ <span class="hljs-attr">id</span>: rows[<span class="hljs-number">0</span>].masked_task_id });
    }, next);
  })
);
</code></pre>
<ul>
<li><p>The <a target="_blank" href="https://github.com/Abazhenov/express-async-handler/blob/master/index.js"><code>express-async-handler</code></a> is mainly used to handle the async errors in my route handlers. It won't be needed anymore when Express 5 drops.</p>
</li>
<li><p>Import the <code>db</code> module to use the <code>tx</code> method. Pass your hand-crafted SQL queries and <a target="_blank" href="https://node-postgres.com/features/queries">parameters</a>.</p>
</li>
</ul>
<p>That's it!</p>
<h3 id="heading-creating-table-schemas">Creating table schemas</h3>
<p>Before you can start querying a database, you need to create tables. Each table contains information about an entity. </p>
<p>But we don't just lump all information about an entity in the same table. We need to organize the information in a way that promotes query performance and data maintainability. And what has helped me in that exercise is a concept called <a target="_blank" href="https://firebase.google.com/docs/database/web/structure-data"><strong>denormalization</strong></a>. </p>
<p>As mentioned, we don't want to store everything about an entity in the same table. For example, say, we have a <code>users</code> table storing <code>fullname</code>, <code>password</code> and <code>email</code>. That's fine so far. </p>
<p>But problem arises when we are also storing the ids of all the projects assigned to a particular user in a separate column in the same table. Instead, I will break them up into separate tables:</p>
<ol>
<li><p>Create the <code>users</code> table. Notice that it's not storing any data related to 'projects':</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">users</span>(
  user_id BIGSERIAL PRIMARY <span class="hljs-keyword">KEY</span>,
  fullname <span class="hljs-built_in">TEXT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  pwd <span class="hljs-built_in">TEXT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  email <span class="hljs-built_in">TEXT</span> <span class="hljs-keyword">UNIQUE</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
);
</code></pre>
</li>
<li><p>Create a <code>projects</code> table to store data solely about a project's details:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> projects(
  project_id BIGSERIAL PRIMARY <span class="hljs-keyword">KEY</span>,
  title <span class="hljs-built_in">TEXT</span>,
  <span class="hljs-keyword">content</span> <span class="hljs-built_in">TEXT</span>,
  due_date TIMESTAMPTZ,
  <span class="hljs-keyword">status</span> <span class="hljs-built_in">SMALLINT</span>,
  created_on TIMESTAMPTZ <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">now</span>()
);
</code></pre>
</li>
<li><p>Create a 'bridge' table about projects' ownerships by associating the ID of an user with the ID of a project that she owns:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> project_ownerships(
  project_id <span class="hljs-built_in">BIGINT</span> <span class="hljs-keyword">REFERENCES</span> projects <span class="hljs-keyword">ON</span> <span class="hljs-keyword">DELETE</span> <span class="hljs-keyword">CASCADE</span>,
  user_id <span class="hljs-built_in">BIGINT</span> <span class="hljs-keyword">REFERENCES</span> <span class="hljs-keyword">users</span> <span class="hljs-keyword">ON</span> <span class="hljs-keyword">DELETE</span> <span class="hljs-keyword">CASCADE</span>,
  PRIMARY <span class="hljs-keyword">KEY</span> (project_id, user_id),
  <span class="hljs-keyword">CONSTRAINT</span> project_user_unique <span class="hljs-keyword">UNIQUE</span> (user_id, project_id)
);
</code></pre>
</li>
<li><p>Finally, to get all the projects that are assigned to a particular user, we will do what relational database do best: <a target="_blank" href="https://www.postgresqltutorial.com/postgresql-joins/"><code>join</code></a>.</p>
</li>
</ol>
<p>I will put all my schemas in a <code>.sql</code> file at my project's root <a href="#schemas-file" id="schemas-file">#</a>:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> EXTENSION <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> <span class="hljs-string">"uuid-ossp"</span>;

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">users</span>(
  user_id BIGSERIAL PRIMARY <span class="hljs-keyword">KEY</span>,
  fullname <span class="hljs-built_in">TEXT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  pwd <span class="hljs-built_in">TEXT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  email <span class="hljs-built_in">TEXT</span> <span class="hljs-keyword">UNIQUE</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  created_on TIMESTAMPTZ <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">now</span>()
);
</code></pre>
<p>Then, I will copy, paste, and run them in pgAdmin:</p>
<p><img src="https://i.imgur.com/gm9YFZF.png" alt="create table schemas in pgadmin" width="600" height="400" loading="lazy"></p>
<p>No doubt there are more advanced ways of doing this, so it's up to you if you want to explore what you like.</p>
<h3 id="heading-dropping-a-database">Dropping a database</h3>
<p>Deleting an entire database to start with a new set of schemas was something I had to do very often at the beginning.</p>
<p>The trick is: Well, you copy, paste, and run the command below in the database's query editor in pgAdmin:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">DROP</span> <span class="hljs-keyword">SCHEMA</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">CASCADE</span>;
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">SCHEMA</span> <span class="hljs-keyword">public</span>;
<span class="hljs-keyword">GRANT</span> <span class="hljs-keyword">ALL</span> <span class="hljs-keyword">ON</span> <span class="hljs-keyword">SCHEMA</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">TO</span> postgres;
<span class="hljs-keyword">GRANT</span> <span class="hljs-keyword">ALL</span> <span class="hljs-keyword">ON</span> <span class="hljs-keyword">SCHEMA</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">TO</span> <span class="hljs-keyword">public</span>;
<span class="hljs-keyword">COMMENT</span> <span class="hljs-keyword">ON</span> <span class="hljs-keyword">SCHEMA</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">IS</span> <span class="hljs-string">'standard public schema'</span>;
</code></pre>
<h3 id="heading-crafting-sql-queries">Crafting SQL queries</h3>
<p>I write my SQL queries in <strong>pgAdmin</strong> to get the data I want out of an API endpoint.</p>
<p>To give a sense of direction to doing that in pgAdmin:
<img src="https://i.imgur.com/54tIRzc.png" alt="writing sql queries in pgadmin editor" width="600" height="400" loading="lazy"></p>
<h4 id="heading-common-table-expressionsctes">Common Table Expressions(CTEs)</h4>
<p>I stumbled upon a pattern called <a target="_blank" href="https://www.postgresql.org/docs/9.1/queries-with.html"><strong>CTEs</strong></a> when I was exploring how I was going to get the data I wanted from disparate tables and structure them as I wished, without doing lots of separate database queries and for-loops.</p>
<p>The way CTE works is simple enough, even though it looks daunting: You write your queries. Each query is given an alias name (<code>q</code>, <code>q1</code>, <code>q3</code>). And a next query can access any previous query's results by their alias name (<code>q1.workspace_id</code>):</p>
<pre><code class="lang-sql"><span class="hljs-keyword">WITH</span> q <span class="hljs-keyword">AS</span> (<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> projects_tasks <span class="hljs-keyword">WHERE</span> task_id=$<span class="hljs-number">1</span>)
, q1 <span class="hljs-keyword">AS</span> (<span class="hljs-keyword">SELECT</span> wp.workspace_id, wp.project_id, q.task_id <span class="hljs-keyword">FROM</span> workspaces_projects wp, q <span class="hljs-keyword">WHERE</span> wp.project_id = q.project_id)
, q3 <span class="hljs-keyword">AS</span> (<span class="hljs-keyword">SELECT</span> q1.workspace_id <span class="hljs-keyword">AS</span> workspace_id, wp.name <span class="hljs-keyword">AS</span> workspace_title, mask_id(q1.project_id) <span class="hljs-keyword">AS</span> project_id, p.title <span class="hljs-keyword">AS</span> project_title, mask_id(t.task_id) <span class="hljs-keyword">AS</span> task_id, t.title, t.content, t.due_date, t.priority, t.status)

<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> q3;
</code></pre>
<p>Almost all the queries in Sametable are written this way.</p>
<h3 id="heading-redis">Redis</h3>
<p>Redis is a NoSQL database that stores data in memory. In Sametable, I used Redis for two purposes:</p>
<ol>
<li>Store a user's session data and basic info from the <code>users</code> table—name, email, and a flag that indicates the user is a subscriber or not—once they have logged in.</li>
<li>Cache the results of some of my Postgresql's queries to avoid having to query the database if the cache is still fresh.</li>
</ol>
<h4 id="heading-installation">Installation</h4>
<p>I'm on a Windows 10 machine with Windows Subsystem Linux (WSL) installed. This was the only guide I followed to install Redis on my machine:</p>
<p><a target="_blank" href="https://redislabs.com/blog/redis-on-windows-10/">https://redislabs.com/blog/redis-on-windows-10/</a></p>
<p>Follow the guide to install WSL if you don't have it already.</p>
<p>Then I will start my local Redis server in WSL bash:</p>
<ol>
<li>Press <kbd>Win</kbd> + <kbd>R</kbd>.</li>
<li>Type <code>bash</code> and enter.</li>
<li>In the terminal, run <code>sudo service redis-server start</code></li>
</ol>
<p>Now install the <a target="_blank" href="https://www.npmjs.com/package/redis"><code>redis</code></a> npm package:</p>
<pre><code class="lang-json">cd server

npm i redis
</code></pre>
<p>Make sure to install it in the <code>server</code>'s <code>package.json</code>, hence the <code>cd server</code>.</p>
<p>Then I create a file named <code>redis.js</code> under <code>server/db</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/db/redis.js</span>

<span class="hljs-keyword">const</span> redis = <span class="hljs-built_in">require</span>(<span class="hljs-string">"redis"</span>);
<span class="hljs-keyword">const</span> { promisify } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"util"</span>);

<span class="hljs-keyword">const</span> redisClient = redis.createClient(
  NODE_ENV === <span class="hljs-string">"production"</span>
    ? {
        <span class="hljs-attr">host</span>: process.env.REDISHOST,
        <span class="hljs-attr">no_ready_check</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">auth_pass</span>: process.env.REDIS_PASSWORD,
      }
    : {}
);

redisClient.on(<span class="hljs-string">"error"</span>, <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"ERR:REDIS:"</span>, err));

<span class="hljs-keyword">const</span> redisGetAsync = promisify(redisClient.get).bind(redisClient);
<span class="hljs-keyword">const</span> redisSetExAsync = promisify(redisClient.setex).bind(redisClient);
<span class="hljs-keyword">const</span> redisDelAsync = promisify(redisClient.del).bind(redisClient);

<span class="hljs-comment">// 1 day expiry</span>
<span class="hljs-keyword">const</span> REDIS_EXPIRATION = <span class="hljs-number">7</span> * <span class="hljs-number">86400</span>; <span class="hljs-comment">// seconds</span>

<span class="hljs-built_in">module</span>.exports = {
  redisGetAsync,
  redisSetExAsync,
  redisDelAsync,
  REDIS_EXPIRATION,
  redisClient,
};
</code></pre>
<ul>
<li><p>By <a target="_blank" href="https://www.npmjs.com/package/redis#options-object-properties">default</a>, <code>node-redis</code> will connect to <code>localhost</code> at port <code>6379</code>. But that might not be the case in production if you host your Redis in a VM. So I provide this object if it's in production mode:</p>
<pre><code class="lang-js">{
   <span class="hljs-attr">host</span>: process.env.REDISHOST,
   <span class="hljs-attr">no_ready_check</span>: <span class="hljs-literal">true</span>,
   <span class="hljs-attr">auth_pass</span>: process.env.REDIS_PASSWORD,
 }
</code></pre>
<ul>
<li>TBH, I'm not entirely sure about the <code>no_ready_check</code>. I got it from this official <a target="_blank" href="https://docs.redislabs.com/latest/rs/references/client_references/client_nodejs/">tutorial</a>.</li>
<li>The <code>auth_pass</code> and <code>host</code> are provided as custom since I host my Redis in a GCE VM where I have set a password on my Redis.</li>
</ul>
</li>
<li><p>I <a target="_blank" href="https://www.npmjs.com/package/redis#promises">promisfy</a> the Redis methods that I will use to make them async to avoid blocking NodeJS's single-thread.</p>
</li>
</ul>
<p>And now you have the Redis for your local development!</p>
<h3 id="heading-error-handling-amp-logging">Error handling &amp; Logging</h3>
<h4 id="heading-error-handling">Error handling</h4>
<p>Error handling in Nodejs has a paradigm which we will explore in 3 different contexts.</p>
<p>To set the stage, we need two things in place first:</p>
<ol>
<li><p>An npm package called <a target="_blank" href="https://www.npmjs.com/package/http-errors">http-errors</a> that will give us a standard error data structure to work with especially in client-side.</p>
<pre><code class="lang-json">npm install http-errors
</code></pre>
</li>
<li><p>We create a custom error handler at the global level to capture <strong>all</strong> propagated errors from the routes or the <code>catch</code> blocks via <code>next(err)</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// app.js</span>
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> app = express();
<span class="hljs-keyword">const</span> createError = <span class="hljs-built_in">require</span>(<span class="hljs-string">"http-errors"</span>);

<span class="hljs-comment">// our central custom error handler</span>
<span class="hljs-comment">// <span class="hljs-doctag">NOTE:</span> DON"T REMOVE THE 'next' even though eslint complains it's not being used!!!</span>
app.use(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">err, req, res, next</span>) </span>{
  <span class="hljs-comment">// errors wrapped by http-errors will have 'status' property defined. Otherwise, it's a generic unexpected error</span>
  <span class="hljs-keyword">const</span> error = err.status
    ? err
    : createError(<span class="hljs-number">500</span>, <span class="hljs-string">"Something went wrong. Notified dev."</span>);

  res.status(error.status).json(error);
});
</code></pre>
<p>As you will see, the general pattern of error handling in Nodejs revolves around the 'middleware' chain and the <code>next</code> parameter:</p>
<blockquote>
<p>Calls to next() and next(err) indicate that the current handler is complete and in what state. next(err) will skip all remaining handlers in the chain except for those that are set up to handle errors . . . <a target="_blank" href="https://expressjs.com/en/guide/error-handling.html">source</a></p>
</blockquote>
<p>Note that although this is a common pattern of handling error in Express, you might want to consider an <a target="_blank" href="https://github.com/goldbergyoni/nodebestpractices/blob/master/sections/errorhandling/centralizedhandling.md">alternative way</a> that's, however, more complicated.</p>
</li>
</ol>
<h5 id="heading-handle-input-validation-errors">Handle input validation errors</h5>
<p>It's a <a target="_blank" href="https://github.com/goldbergyoni/nodebestpractices#-610-validate-incoming-json-schemas">good practise</a> to validate a user's inputs both in the client and server-side. </p>
<p>At the server-side, I use a library called <a target="_blank" href="https://express-validator.github.io/docs/">'express-validator'</a> to do the job. If any input is invalid, I will handle it by responding with an HTTP code and an error message to inform the user about it.</p>
<p>For example, when an email provided by a user is invalid, we will exit early by creating an error object with the 'http-errors' library, and then pass it to the <code>next</code> function:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { body, validationResult } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express-validator"</span>);

router.post(
  <span class="hljs-string">"/login"</span>,
  upload.none(),
  [body(<span class="hljs-string">"email"</span>, <span class="hljs-string">"Invalid email format"</span>).isEmail()],
  asyncHandler(<span class="hljs-keyword">async</span> (req, res, next) =&gt; {
    <span class="hljs-keyword">const</span> errors = validationResult(req);
    <span class="hljs-keyword">if</span> (!errors.isEmpty()) {
      <span class="hljs-keyword">return</span> next(createError(<span class="hljs-number">422</span>, errors.mapped()));
    }

    res.json({});
  })
);
</code></pre>
<p>The following response will be sent to the client:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Unprocessable Entity"</span>,
  <span class="hljs-attr">"email"</span>: {
    <span class="hljs-attr">"value"</span>: <span class="hljs-string">"hello@mail.com232"</span>,
    <span class="hljs-attr">"msg"</span>: <span class="hljs-string">"Invalid email format"</span>,
    <span class="hljs-attr">"param"</span>: <span class="hljs-string">"email"</span>,
    <span class="hljs-attr">"location"</span>: <span class="hljs-string">"body"</span>
  }
}
</code></pre>
<p>Then it's up to you what you want to do with it. For example, you can access the <code>email.msg</code> property to display the error message below the email input field.</p>
<h5 id="heading-handle-errors-from-business-logic">Handle errors from business logic</h5>
<p>Let's say we have a situation where a user entered an email that didn't exist in the database. In that case, we need to tell the user to try again:</p>
<pre><code class="lang-javascript">router.post(
  <span class="hljs-string">"/login"</span>,
  upload.none(),
  asyncHandler(<span class="hljs-keyword">async</span> (req, res, next) =&gt; {
    <span class="hljs-keyword">const</span> { email, password } = req.body;

    <span class="hljs-keyword">const</span> { rowCount } = <span class="hljs-keyword">await</span> db.query(
      <span class="hljs-string">`SELECT * FROM users WHERE email=($1)`</span>,
      [email]
    );

    <span class="hljs-keyword">if</span> (rowCount === <span class="hljs-number">0</span>) {
      <span class="hljs-comment">// issue an error with generic message</span>
      <span class="hljs-keyword">return</span> next(
        createError(<span class="hljs-number">422</span>, <span class="hljs-string">"Please enter a correct email and password"</span>)
      );
    }

    res.json({});
  })
);
</code></pre>
<p>Remember, any error object passed to 'next'(<code>next(err)</code>) will be captured by the custom error handler that we have set above.</p>
<h5 id="heading-handle-unexpected-errors-from-database">Handle unexpected errors from database</h5>
<p>I pass the route handler's <code>next</code> to my db's <a href="#db-wrapper">transaction</a> wrapper function to handle any unexpected erorrs.</p>
<pre><code class="lang-javascript">router.post(
  <span class="hljs-string">"/invite"</span>,
  <span class="hljs-keyword">async</span> (req, res, next) =&gt; {
    db.tx(<span class="hljs-keyword">async</span> (client) =&gt; {
          <span class="hljs-keyword">const</span> {
            rows,
            rowCount,
          } = <span class="hljs-keyword">await</span> client.query(
            <span class="hljs-string">`SELECT mask_id(user_id) AS user_id, status FROM users WHERE users.email=$1`</span>,
            [email]
          );
    }, next)
)
</code></pre>
<h4 id="heading-logging">Logging</h4>
<p>When an error occurs, it's a common practice to 1) Log it to a system for records, and 2) Automatically notify you about it.</p>
<p>There are many tools out there in this area. But I ended up with two of them:</p>
<ul>
<li><a target="_blank" href="https://sentry.io/welcome/"><strong>Sentry</strong></a> for storing details (e.g. stack traces) of my errors, and displaying them on their web-based dashboard.</li>
<li><a target="_blank" href="https://github.com/pinojs/pino"><strong>pino</strong></a> to enable logging in my Nodejs.</li>
</ul>
<p><strong>Why Sentry</strong>? Well, it was recommended by lots of devs and small startups. It offers 5000 errors you can send per month for free. For perspective, if you are operating a small side project and careful about it, I would say that'd last you until you can afford a more luxurious vendor or plan. </p>
<p>Another option worth exploring is <a target="_blank" href="https://www.honeybadger.io/">honeybadger.io</a> with more generous free-tier but without a <a target="_blank" href="https://getpino.io/#/docs/transports">pino transport</a>.</p>
<p><strong>Why Pino</strong>- Why not the official SDK provided by Sentry? Because Pino has <a target="_blank" href="https://github.com/pinojs/pino#low-overhead">'low overhead'</a>, whereas, Sentry SDK, although it gives you a more complete picture of an error, seemed to have a complex <a target="_blank" href="https://github.com/getsentry/sentry-javascript/issues/1762">memory issue</a> that I couldn't see myself being able to circumvent.</p>
<p>With that, here is how the logging system is hooked up in Sametable:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/lib/logger.js</span>

<span class="hljs-comment">// install missing packages</span>
<span class="hljs-keyword">const</span> pino = <span class="hljs-built_in">require</span>(<span class="hljs-string">"pino"</span>);
<span class="hljs-keyword">const</span> { createWriteStream } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"pino-sentry"</span>);
<span class="hljs-keyword">const</span> expressPino = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express-pino-logger"</span>);

<span class="hljs-keyword">const</span> options = { <span class="hljs-attr">name</span>: <span class="hljs-string">"sametable"</span>, <span class="hljs-attr">level</span>: <span class="hljs-string">"error"</span> };

<span class="hljs-comment">// SENTRY_DSN is provided by Sentry. Store it as env var in the .env file.</span>
<span class="hljs-keyword">const</span> stream = createWriteStream({ <span class="hljs-attr">dsn</span>: process.env.SENTRY_DSN });

<span class="hljs-keyword">const</span> logger = pino(options, stream);
<span class="hljs-keyword">const</span> expressLogger = expressPino({ logger });

<span class="hljs-built_in">module</span>.exports = {
  expressLogger, <span class="hljs-comment">// use it like app.use(expressLogger) -&gt; req.log.info('haha)</span>
  logger,
};
</code></pre>
<p>Rather than attaching the logger(<code>expressLogger</code>) as a middleware at the top of the chain(<code>app.use(expressLogger)</code>), I use the <code>logger</code> object only where I want to log an error.</p>
<p>For example, the custom global error handler uses the <code>logger</code> object:</p>
<pre><code class="lang-javascript">app.use(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">err, req, res, next</span>) </span>{
  <span class="hljs-keyword">const</span> error = err.status
    ? err
    : createError(<span class="hljs-number">500</span>, <span class="hljs-string">"Something went wrong. Notified dev."</span>);

  <span class="hljs-keyword">if</span> (isProduction) {
    <span class="hljs-comment">// LOG THIS ERROR IN MY SENTRY DASHBOARD</span>
    logger.error(error);
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Custom error handler:"</span>, error);
  }

  res.status(error.status).json(error);
});
</code></pre>
<p>That's it! And don't forget to enable email <strong>notification</strong> in your Sentry dashboard to get an alert when your Sentry receives an error! ❤️</p>
<h3 id="heading-permalink-for-url-sharing">Permalink for URL Sharing</h3>
<p>We have seen URLs consist of cryptic alphanumeric string such as those on Youtube: <code>https://youtube.com/watch?v=upyjlOLBv5o</code>. This URL points to a specific video, which can be shared with someone by sharing the URL. The key component in the URL representing the video is the unique ID at the end: <code>upyjlOLBv5o</code>. </p>
<p>We see this kind of ID in other sites too: <code>vimeo.com/259411563</code> and subscription's ID in Stripe <code>sub_aH2s332nm04</code>.</p>
<p>As far as I know, there are three ways to achieve this outcome:</p>
<ol>
<li><p><a target="_blank" href="https://stackoverflow.com/a/41988979/73323">Generate the ID when inserting data in your database</a>. The generated ID will be the ID in your <code>id</code> column rather than the auto-increment ones:</p>
<p>| id         | title        |
| ---------- | ------------ |
| owmCAx552Q | How to cry   |
| ZIofD6l3X9 | How to smile |</p>
</li>
</ol>
<p>Then you will expose these IDs in public-facing URLs: <code>https://example.com/task/owmCAx552Q</code>. Given this URL to your backend, you can retrieve the respective resource from the database:</p>
<pre><code class="lang-javascript">   router.get(<span class="hljs-string">"/task/:taskId"</span>, <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> {
     <span class="hljs-keyword">const</span> { taskId } = req.params;
     <span class="hljs-comment">// SELECT * FROM tasks WHERE id=&lt;taskId&gt;</span>
   });
</code></pre>
<p>The downsides to this method that I know of:</p>
<ul>
<li>The IDs might be sensitive information to be exposed publicly like that.</li>
<li>These IDs are detrimental to the performance of indexing and 'joining' on your tables.</li>
</ul>
<ol start="2">
<li><p>You keep auto-incrementing your IDs in your tables, but you will represent them by <a target="_blank" href="https://hashids.org/postgresql/">generating their alphanumeric counterpart during database operations</a>:</p>
<pre><code class="lang-sql">  <span class="hljs-keyword">SELECT</span> hash_encode(<span class="hljs-number">123</span>, <span class="hljs-string">'this is my salt'</span>, <span class="hljs-number">10</span>); <span class="hljs-comment">-- Result: 4xpAYDx0mQ</span>
  <span class="hljs-keyword">SELECT</span> hash_decode(<span class="hljs-string">'4xpAYDx0mQ'</span>, <span class="hljs-string">'this is my salt'</span>, <span class="hljs-number">10</span>); <span class="hljs-comment">-- Result: 123</span>
</code></pre>
<p>I had trouble integrating this library on my Windows machine. So I went with the next option.</p>
</li>
<li><p><a target="_blank" href="https://old.reddit.com/r/PostgreSQL/comments/6gw866/best_practice_for_id_system_that_is_obscure_for/diu8cr1/">Similar to the second option above but different approach</a>. This will generate numeric ID: <code>https://example.com/task/2013732563294762</code></p>
</li>
</ol>
<h2 id="heading-user-authentication-system">User Authentication System</h2>
<p>A user authentication system can get very complicated if you need to support things like SSO and third-party OAuth providers. That's why we have third-party tools such as Auth0, Okta, and PassportJS to abstract that out for us. But those tools cost: vendor lock-in, more Javascript payload, and cognitive overhead.</p>
<p>I would argue that if you are starting out and just need <em>some</em> <em>kind of</em> authentication system so you can move on to other parts of your app, and at the same time, overwhelmed by all the dated tutorials that deal with stuff you don't use, well, chances are all you need is the good old way of doing authentication: <strong>Session cookie</strong> with <strong>email</strong> and <strong>password</strong>! And we are not talking about 'JWT' either! None of that.</p>
<h3 id="heading-guide-1">Guide</h3>
<p><a target="_blank" href="https://medium.com/@kilgarenone/easily-implements-user-authentication-in-nodejs-b22bdb6f15bc">Here is a guide</a> I ended up writing. Follow it and you got yourself a user authentication system!</p>
<h2 id="heading-email">Email</h2>
<p>Currently, in Sametable, the only emails it sends are of 'transactional' type like sending a reset password email when users reset their password.</p>
<p>There are two ways to send emails in Nodejs:</p>
<ol>
<li><p><strong>Roll your own</strong> with <a target="_blank" href="https://nodemailer.com/about/">Nodemailer</a>.</p>
<p>I wouldn't go down this path because although sending one email might seem a trivial task, doing it 'at scale' is hard; every email must be sent successfully; and they must not end up in a user's spam folder; and other things I'm not aware of.</p>
</li>
<li><p>Choose one of the <strong>email service providers</strong>.</p>
</li>
</ol>
<p>Many email services offer a free-tier plan offering a limited number of emails you can send per month/day for free. When I started exploring this space for Sametable in October 2019, Mailgun stood out to be a no-brainer—It offers 10,000 emails for free per month! </p>
<p>But, sadly, as I was researching for this section write-up, I learned that it no longer offers that. Despite that, though, I would still stick to Mailgun, on their <a target="_blank" href="https://www.mailgun.com/pricing">pay-as-you-go</a> plan: 1000 emails sent will cost you 80 cents.</p>
<p>If you would rather not pay a cent for whatever reason, here are two options for you that I could find:</p>
<ul>
<li><a target="_blank" href="https://www.mailjet.com/pricing/">https://www.mailjet.com/pricing/</a></li>
<li><a target="_blank" href="https://www.sendinblue.com/pricing/">https://www.sendinblue.com/pricing/</a></li>
</ul>
<p>But do go down this path while being aware there's no guarantee that these free-tier plans will stay that way forever as was the case with Mailgun.</p>
<h3 id="heading-implementation">Implementation</h3>
<h4 id="heading-wrapper-file">Wrapper file</h4>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/lib/email.js</span>

<span class="hljs-comment">// Run 'npm install mailgun-js' in your 'server' folder</span>
<span class="hljs-keyword">const</span> mailgun = <span class="hljs-built_in">require</span>(<span class="hljs-string">"mailgun-js"</span>);

<span class="hljs-keyword">const</span> DOMAIN = <span class="hljs-string">"mail.sametable.app"</span>;

<span class="hljs-keyword">const</span> mg = mailgun({
  <span class="hljs-attr">apiKey</span>: process.env.MAILGUN_API_KEY,
  <span class="hljs-attr">domain</span>: DOMAIN,
});

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">send</span>(<span class="hljs-params">data</span>) </span>{
  mg.messages().send(data, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">error</span>) </span>{
    <span class="hljs-keyword">if</span> (!error) <span class="hljs-keyword">return</span>;
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Email send error:"</span>, error);
  });
}

<span class="hljs-built_in">module</span>.exports = {
  send,
};
</code></pre>
<h4 id="heading-usage-2">Usage</h4>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> mailer = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../lib/email"</span>);

<span class="hljs-comment">// Simplified for only email-related stuff</span>
router.post(
  <span class="hljs-string">"/resetPassword"</span>,
  upload.none(),
  <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { email } = req.body;
    <span class="hljs-keyword">const</span> data = {
      <span class="hljs-attr">from</span>: <span class="hljs-string">"Sametable &lt;feedback@sametable.app&gt;"</span>,
      <span class="hljs-attr">to</span>: email,
      <span class="hljs-attr">subject</span>: <span class="hljs-string">"Reset your password"</span>,
      <span class="hljs-attr">text</span>: <span class="hljs-string">`Click this link to reset your password: https://example.com?token=1234`</span>,
    };
    mailer.send(data);
    res.json({});
  })
);
</code></pre>
<h3 id="heading-email-templates">Email templates</h3>
<p>Each type of email you send could have its own email template whose content can be varied with dynamic values you can provide.</p>
<h4 id="heading-tool">Tool</h4>
<p><a target="_blank" href="https://mjml.io/"><strong>mjml</strong></a> is the tool I use to build my email templates. Sure, there are many drag-and-drop email builders out there that don't intimidate with the sight of 'codes'. But if you know just basic React/HTML/CSS, mjml would give you great usability and maximum flexibility.</p>
<p>It's easy to <a target="_blank" href="https://mjml.io/getting-started/1">get started</a>. Like the email builders, you compose a template with a bunch of reusable components, and you customize them by providing values to their props.</p>
<p>Here are the places where I would write my templates:</p>
<ul>
<li>This <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=attilabuti.vscode-mjml">VSCode extension</a></li>
<li><a target="_blank" href="https://mjml.io/try-it-live">Live code editor</a></li>
</ul>
<h4 id="heading-example-template">Example template</h4>
<details>
  <summary>Email Template</summary>

<code>html
&lt;mjml&gt;
  &lt;mj-head&gt;
    &lt;mj-attributes&gt;
      &lt;mj-class
        name="font-family"
        font-family="-apple-system,system-ui,BlinkMacSystemFont,'Segoe UI',sans-serif"
      /&gt;
      &lt;mj-class name="fw-600" font-weight="600" /&gt;
    &lt;/mj-attributes&gt;
  &lt;/mj-head&gt;
  &lt;mj-body&gt;
    &lt;mj-section&gt;
      &lt;mj-column&gt;
        &lt;mj-image
          width="150px"
          src="https://www.dl.dropboxusercontent.com/s/pgtwrnfa3lqkf5r/sametable_logo_with_text.png"
        /&gt;
      &lt;/mj-column&gt;
    &lt;/mj-section&gt;
    &lt;mj-section&gt;
      &lt;mj-column&gt;
        &lt;mj-text align="center" font-size="20px" mj-class="font-family"
          &gt;{{assigner_name}} assigned a project to you&lt;/mj-text
        &gt;
        &lt;mj-spacer height="10px" /&gt;
        &lt;mj-text align="center" font-size="25px" mj-class="font-family fw-600"
          &gt;{{project_title}}&lt;/mj-text
        &gt;
        &lt;mj-spacer height="25px" /&gt;
        &lt;mj-button
          font-size="16px"
          mj-class="font-family fw-600"
          background-color="#000"
          color="white"
          href="{{invite_link}}"
          &gt;View the project&lt;/mj-button
        &gt;
      &lt;/mj-column&gt;
    &lt;/mj-section&gt;
    &lt;mj-spacer height="55px" /&gt;
    &lt;mj-section background-color="#EEEBE7" padding="25px 40px"&gt;
      &lt;mj-column&gt;
        &lt;mj-text
          align="center"
          color="#45495d"
          font-size="15px"
          line-height="14px"
        &gt;
          Problems or questions? Feel free to reply to this email.
        &lt;/mj-text&gt;
        &lt;mj-text padding="30px 0 0 0" align="center" font-size="16px"&gt;
          Made with ❤️ by
          &lt;a href="https://twitter.com/kheohyeewei"&gt;@kheohyeewei&lt;/a&gt;
        &lt;/mj-text&gt;
      &lt;/mj-column&gt;
    &lt;/mj-section&gt;
  &lt;/mj-body&gt;
&lt;/mjml&gt;</code>

</details>

<h5 id="heading-result">Result</h5>
<p><img src="https://i.imgur.com/JnaSDYJ.png" width="600" height="400" alt="JnaSDYJ" loading="lazy"></p>
<p>Notice the placeholder names that are wrapped in double curly brackets such as <code>{{project_title}}</code>. They will be replaced with their corresponding value by, in my case, Mailgun, before being sent out.</p>
<h4 id="heading-integration-with-mailgun">Integration with Mailgun</h4>
<p>First, generate HTML from your mjml templates. You are able to do that with the VSCode extension or the web-based editor:</p>
<p><img src="https://i.imgur.com/O9mnBvN.png" alt="generate html in mjml online code editor" width="600" height="400" loading="lazy"></p>
<p>Then create a new template on your Mailgun dashboard:</p>
<p><img src="https://i.imgur.com/OkvLxiT.png" alt="create message template on mailgun dashboard" width="600" height="400" loading="lazy"></p>
<h4 id="heading-send-an-email-with-mailgun-in-nodejs">Send an email with Mailgun in Nodejs</h4>
<p>Inside a route:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> data = {
  <span class="hljs-attr">from</span>: <span class="hljs-string">"Sametable &lt;feedback@sametable.app&gt;"</span>,
  <span class="hljs-attr">to</span>: email,
  <span class="hljs-attr">subject</span>: <span class="hljs-string">`Hello`</span>,
  <span class="hljs-attr">template</span>: <span class="hljs-string">"invite_project"</span>, <span class="hljs-comment">// the template's name you gave when you created it in mailgun</span>
  <span class="hljs-string">"v:invite_link"</span>: inviteLink,
  <span class="hljs-string">"v:assigner_name"</span>: fullname,
  <span class="hljs-string">"v:project_title"</span>: title,
};

mailer.send(data);
</code></pre>
<p>Notice that, to associate a value with a placeholder name in a template: <code>"v:project_title":'Project Mario'</code>.</p>
<h3 id="heading-how-to-get-one-of-those-hiexamplecom">How to get one of those <code>hi@example.com</code></h3>
<p>It's an email address people use to contact you about your SaaS, rather than with a <code>lola887@hotmail.com</code>.</p>
<p>There are three options on my radar:</p>
<ol>
<li>If you are on Mailgun, follow <a target="_blank" href="https://renzo.lucioni.xyz/mail-forwarding-with-mailgun/">this guide</a>. However, the new pay-as-you-go tier has excluded the feature(<code>Inbound Email Routing</code>) that makes this possible. So perhaps the next option;</li>
<li>If I ever get kicked out of my '10,000' free-tier in Mailgun, I would give this a shot https://forwardemail.net/en</li>
<li>If all else failed, pay for <a target="_blank" href="https://gsuite.google.com.my/intl/en_my/products/gmail/">'Gmail on G Suite'</a>.</li>
</ol>
<h2 id="heading-tenancy">Tenancy</h2>
<p>When an organization, say, Acme Inc., signs up on your SaaS, it's considered a 'tenant' — They 'occupy' a spot on your service.</p>
<p>While I'd heard of the 'multi-tenancy' term being associated with a SaaS before, I never had the slightest idea about implementing it. I always thought that it'd involve some cryptic computer-sciency maneuvering that I couldn't possibly have figured it all out by myself.</p>
<p>Fortunately, there is an easy way to do 'multi-tenancy':</p>
<blockquote>
<p>Single database; all clients share the same tables; each client has a <code>tenant_id</code>; queries the database as per an API request by <code>WHERE tenant_id = $ID</code>.</p>
</blockquote>
<p>So don't worry—If you know basic SQL (again indicating the importance of mastering the basics in anything you do!), you should have a clear picture on the steps required to implement this.</p>
<p>Here are three instrumental resources about 'multi-tenancy' I bookmarked before:</p>
<ul>
<li><a target="_blank" href="https://stackoverflow.com/a/47783180/73323">https://stackoverflow.com/a/47783180/73323</a></li>
<li><a target="_blank" href="https://stackoverflow.com/a/44530588/73323">https://stackoverflow.com/a/44530588/73323</a></li>
<li><a target="_blank" href="https://blog.checklyhq.com/building-a-multi-tenant-saas-data-model/">https://blog.checklyhq.com/building-a-multi-tenant-saas-data-model/</a></li>
</ul>
<h2 id="heading-domain-name">Domain name</h2>
<p>Sametable.app domain and all its DNS records are hosted in <a target="_blank" href="https://www.namecheap.com/"><strong>NameCheap</strong></a>. I was on <a target="_blank" href="https://www.hover.com/">hover</a> before(it still hosts my personal website's domain). But I hit a limitation there when I tried to enter my Mailgun's DKIM value. Namecheap also has more competitive prices in my experience.</p>
<p>At which stage in your SaaS development should you get a domain name? Well, I would say not until when the lack of a DNS registrar is blocking your development. In my case, I deferred it until I had to integrate Mailgun which requires creating a bunch of DNS records in a domain.</p>
<h3 id="heading-how-to-get-one-of-those-appexamplecom">How to get one of those <code>app.example.com</code></h3>
<p>You know those URLs that has a <code>app</code> in front of it like <code>app.example.io</code>? Yea, that's a 'custom domain' with the 'app' as its 'subdomain'. And it all started with having a domain name. </p>
<p>So go ahead and get one in Namecheap or whatever. Then, in my case with Firebase, just <a target="_blank" href="https://firebase.google.com/docs/hosting/custom-domain">follow this tutorial</a> and you will be fine.</p>
<h2 id="heading-deployment">Deployment</h2>
<p>Ugh. This was a stage where I struggled for the longest time ?. It was one hell of a journey where I found myself doubling down on a cloud platform but end up bailing out as I found out their downsides to optimize for developer experience, costs, quota, and performance(latency).</p>
<p>The journey started with me jumping head-first(bad idea) into Digital Ocean since I saw it recommended a lot in the IndieHackers forum. And sure enough, I managed to get my Nodejs up and running in a VM by following <a target="_blank" href="https://coderrocketfuel.com/article/create-and-deploy-an-express-rest-api-to-a-digitalocean-server#configure-and-deploy-your-node-js-app">closely</a> the <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-18-04">tutorials</a>. </p>
<p>Then I found out that the <a target="_blank" href="https://www.digitalocean.com/products/spaces/">DO Space</a> wasn't exactly AWS S3—It <a target="_blank" href="https://ideas.digitalocean.com/ideas/DO-I-318">can't</a> host my SPA. </p>
<p>Although I <a target="_blank" href="https://coderrocketfuel.com/article/deploy-a-create-react-app-website-to-digitalocean">could have</a> hosted it in my droplet and <a target="_blank" href="https://www.youtube.com/watch?v=2X_Tp_G7aTs">hook up</a> a third-party CDN like CloudFlare to the droplet, it seemed to me unnecessarily convoluted compared to the S3+Cloudfront setup. I was also using a DO's Managed Database(Postgresql) because I didn't want to manage my DB and tweak in the <code>*.config</code> files myself. That costs a fixed \$15/month.</p>
<p>Then I learned about <a target="_blank" href="https://aws.amazon.com/lightsail/">AWS Lightsail</a> which is a mirror image of DO, but to my surprise, with more competitive <a target="_blank" href="https://aws.amazon.com/lightsail/pricing/">quota</a> at a given price point:</p>
<p><strong>VM at \$5/month</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>AWS Lightsail</td><td>Digital Ocean</td></tr>
</thead>
<tbody>
<tr>
<td>1 GB Memory</td><td>1 GB Memory</td></tr>
<tr>
<td>1 Core Processor</td><td>1 Core Processor</td></tr>
<tr>
<td><strong>40 GB</strong> SSD Disk</td><td>25 GB SSD Disk</td></tr>
<tr>
<td><strong>2 TB</strong> transfer</td><td>1 TB transfer</td></tr>
</tbody>
</table>
</div><p><strong>Managed database at \$15/month</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>AWS Lightsail</td><td>Digital Ocean</td></tr>
</thead>
<tbody>
<tr>
<td>1 GB Memory</td><td>1 GB Memory</td></tr>
<tr>
<td>1 Core Processor</td><td>1 Core Processor</td></tr>
<tr>
<td><strong>40 GB</strong> SSD Disk</td><td>10 GB SSD Disk</td></tr>
</tbody>
</table>
</div><p>So I started betting on Lightsail instead. But, the \$15/month for a managed database in Lightsail got to me at one point. I didn't want to have to pay that money when I wasn't even sure that I would ever have any paying customers.</p>
<p>At this point, I supposed that I had to get my hands dirty to optimize for the cost factor. So I started looking into wiring AWS EC2, RDS, etc. But there were just too many of AWS-specific things I had to pick up, and the AWS doc wasn't exactly helping either—It's one rabbit hole after another just to do one thing because I just needed something to host my SPA and Nodejs for goodness sake!</p>
<p>Then I checked back in IndieHacker for a sanity check, and came across <a target="_blank" href="https://render.com/">render.com</a>. It seemed perfect! It's one of those tools that are on a mission '<em>so you can focus on building your app</em>'. The tutorials were short and got you up and running in no time. And here is the '<em>but</em>'—It was <a target="_blank" href="https://render.com/pricing">expensive</a>:</p>
<p><strong>Comparison of Lightsail and Render at their lowest price point</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>AWS Lightsail(\$3.50/mo)</td><td>Render(\$7/mo)</td></tr>
</thead>
<tbody>
<tr>
<td>512 GB Memory</td><td>512 MB Memory</td></tr>
<tr>
<td>1 Core Processor</td><td>Shared Processor</td></tr>
<tr>
<td>20 GB SSD Disk</td><td>$0.25/GB/mo SSD Disk(20GB = $5/mo)</td></tr>
<tr>
<td>1 TB transfer</td><td>100 GB/mo. $0.10/GB above that(1TB = $90/mo)</td></tr>
</tbody>
</table>
</div><p>And that's just for hosting my Nodejs!</p>
<p>So what now?! Do I just say <em>f*** it</em> and do whatever it takes to 'ship it'?</p>
<p>But I held my ground. I revisited AWS again. I still believed AWS was the answer because everyone else is singing its song. I must be missing something! </p>
<p>This time I considered their higher-level tools like AWS AppSync and Amplify. But I couldn't overlook the fact that both of them force me to completely work by their standards and library. So at this point, I'd had it with AWS, and turned to another...platform: <strong>Google Cloud Platform(GCP)</strong>.</p>
<p>Sametable's Nodejs, Redis, and Postgresql are hosted on <strong>GCP</strong>.</p>
<p>The thing that drew me to GCP was its documentation—It's much more linear; code snippets everywhere for your specific language; step-by-step guides about the common things you would do for a web app. Plus, it's serverless! Which means your cost is proportional to your usage.</p>
<h3 id="heading-deploy-nodejs">Deploy Nodejs</h3>
<p>The GAE <a target="_blank" href="https://cloud.google.com/appengine/docs/the-appengine-environments">'standard environment'</a> hosts my Nodejs.</p>
<h4 id="heading-cost">Cost</h4>
<p>GAE's standard environment has <a target="_blank" href="https://cloud.google.com/free/docs/gcp-free-tier#always-free-usage-limits">free quota</a> unlike the 'flexible environment'. Beyond that, you will pay only if somebody is using your SaaS ?.</p>
<h4 id="heading-guide-2">Guide</h4>
<p>This was the <em>only</em> guide I relied on. It was my north star. It covers Nodejs, Postgresql, Redis, file storage, and more:</p>
<blockquote>
<p><a target="_blank" href="https://cloud.google.com/appengine/docs/standard/nodejs">https://cloud.google.com/appengine/docs/standard/nodejs</a></p>
</blockquote>
<p>Start with the <a target="_blank" href="https://cloud.google.com/appengine/docs/standard/nodejs/quickstart">'Quick Start'</a> tutorial because it will set you up with the <code>gcloud cli</code> which you are going to need when following the rest of the guides, where you will find commands you can run to follow along. </p>
<p>If you aren't comfortable with the CLI environment, the guides will provide alternative steps to achieve the same thing on the GCP dashboard. I love it.</p>
<p>I noticed that while going through the GCP doc, I never had to open more than 4 tabs in my browser. It was the complete opposite with AWS doc—My browser would be <em>packed</em> with it.</p>
<h3 id="heading-deploy-postgresql">Deploy Postgresql</h3>
<h4 id="heading-guide-3">Guide</h4>
<p><a target="_blank" href="https://cloud.google.com/sql/docs/postgres/connect-app-engine-standard">https://cloud.google.com/sql/docs/postgres/connect-app-engine-standard</a></p>
<p>Just follow it and you will be fine.</p>
<h4 id="heading-cost-1">Cost</h4>
<p>An instance of Cloud SQL runs a full virtual machine. And once a VM has been provisioned, it won't automatically turn itself off when, for example, it has not seen any usage for 15 minutes. So you will be billed for every hour an instance is running for an entire month unless it'd been manually stopped.</p>
<p>The primary factor that will affect your cost here, particularly in the early days, is the grade of the <a target="_blank" href="https://cloud.google.com/sql/pricing#2nd-gen-instance-pricing">machine type</a>. The default machine type for a Cloud SQL is a <code>db-n1-standard-1</code>, and the 'cheapest' one you can get is a <code>db-f1-micro</code>:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>db-n1-standard-1</td><td>db-f1-micro</td><td>Digital Ocean Managed DB</td></tr>
</thead>
<tbody>
<tr>
<td>1 vCPU</td><td>1 shared vCPU</td><td>1 vCPU</td></tr>
<tr>
<td>3.75GB Memory</td><td>0.6GB Memory</td><td>1GB Memory</td></tr>
<tr>
<td>10GB SSD Storage</td><td>10GB SSD Storage</td><td>10GB SSD Storage</td></tr>
<tr>
<td><a target="_blank" href="https://cloud.google.com/products/calculator/#id=c0040a15-933d-4dc3-8022-1428fc210050">~USD 51.01</a></td><td><a target="_blank" href="https://cloud.google.com/sql/pricing#2nd-gen-instance-pricing">~USD 9.37</a></td><td><a target="_blank" href="https://www.digitalocean.com/pricing/#managed-databases">USD 15.00</a></td></tr>
</tbody>
</table>
</div><p>The other two cost factors are <a target="_blank" href="https://cloud.google.com/sql/pricing#pg-storage-networking-prices">storage and network egress</a>. But they are charged monthly, so they probably won't have as big of an impact on the bill of your nascent SaaS.</p>
<p>If you find the price tags to be too hefty to your liking, keep in mind that they are a <em>managed</em> database. You are paying for all the times and anxiety saved from doing devops on your database. For me, it's worth it.</p>
<h3 id="heading-setup-schemas-in-production-database">Setup schemas in production database</h3>
<p>Now that I have got a database deployed for production, it's time to dress it up with all my schemas from the <code>.sql</code> <a href="#schemas-file">file</a>. To do that, I need to connect to the database from pgAdmin:</p>
<ol>
<li><a target="_blank" href="https://cloud.google.com/sql/docs/postgres/external-connection-methods">https://cloud.google.com/sql/docs/postgres/external-connection-methods</a></li>
<li>There you will find a table with a list of options for connecting from an external application. I went with the first one: <strong>Public IP address with SSL</strong>. Follow all the guides in the 'More information' column and you will have all the information needed to create a server in your pgAdmin. You will be fine. If not, <a href="mailto:oldjoy@protonmail.com">email me</a> and I will provide assistance.</li>
</ol>
<h3 id="heading-deploy-redis">Deploy Redis</h3>
<p>If you were following the main guide about Nodejs, you can't miss <a target="_blank" href="https://cloud.google.com/appengine/docs/standard/nodejs/using-memorystore">this guide</a> about setting up your Redis in MemoryStore. But I figured it would be more cost-effective to <strong>host my Redis in a Google Compute Engine</strong>(GCE) which has, unlike MemoryStore, free quota in certain aspects. (<a target="_blank" href="https://github.com/ripienaar/free-for-dev#major-cloud-providers">See this</a> for comparison of free quota across different cloud platforms)</p>
<h4 id="heading-guide-4">Guide</h4>
<ol>
<li><a target="_blank" href="https://cloud.google.com/community/tutorials/setting-up-redis">Setup</a> Redis in a VM.</li>
</ol>
<p>2) <a target="_blank" href="https://cloud.google.com/appengine/docs/standard/python/connecting-vpc">Setup</a> VPC:</p>
<blockquote>
<p>Serverless VPC Access enables you to connect from your App Engine app directly to your VPC network, <strong>allowing access to Compute Engine VM instances</strong>, Memorystore instances, and any other resources with an internal IP address.</p>
</blockquote>
<ol start="3">
<li><p>In your <code>app.yaml</code> file:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">vpc_access_connector:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">"&lt;YOURS_HERE&gt;"</span>

<span class="hljs-attr">env_variables:</span>
  <span class="hljs-attr">REDIS_PASSWORD:</span> <span class="hljs-string">"&lt;PASSWORD_YOU_SET_IN_A_GUIDE_ABOVE&gt;"</span>
  <span class="hljs-attr">REDISHOST:</span> <span class="hljs-string">"&lt;INTERNAL_IP_OF_YOUR_VM&gt;"</span>
  <span class="hljs-attr">REDISPORT:</span> <span class="hljs-string">"6379"</span> <span class="hljs-comment"># default port when install redis</span>
</code></pre>
<figure>
 <img src="https://i.imgur.com/N4bqhcT.png" alt="internal ip of a GCE vm" width="600" height="400" loading="lazy">
 <figcaption>
     <small>Internal IP of a GCE</small>
 </figcaption>
</figure>
</li>
<li><p>Finally, in <code>lib/redis.js</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> redis = <span class="hljs-built_in">require</span>(<span class="hljs-string">"redis"</span>);

<span class="hljs-keyword">const</span> redisClient = redis.createClient(
  NODE_ENV === <span class="hljs-string">"production"</span>
    ? {
        <span class="hljs-attr">host</span>: process.env.REDISHOST,
        <span class="hljs-attr">port</span>: process.env.REDISPORT, <span class="hljs-comment">// default to 6379 if wasn't set</span>
        <span class="hljs-attr">no_ready_check</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">auth_pass</span>: process.env.REDIS_PASSWORD,
      }
    : {} <span class="hljs-comment">// just use the default: localhost and ports</span>
);

<span class="hljs-built_in">module</span>.exports = {
  redisClient,
};
</code></pre>
</li>
</ol>
<h3 id="heading-file-storage">File Storage</h3>
<p><a target="_blank" href="https://cloud.google.com/storage/docs">Cloud Storage</a> is what you need for your users to upload their files such as images which they will need to retrieve and possibly display later.</p>
<h4 id="heading-cost-2">Cost</h4>
<p>There is a <a target="_blank" href="https://cloud.google.com/free/docs/gcp-free-tier#always-free-usage-limits">free tier</a> for Cloud Storage too.</p>
<h4 id="heading-guide-5">Guide</h4>
<ul>
<li>You will be fine:
<a target="_blank" href="https://cloud.google.com/appengine/docs/standard/nodejs/using-cloud-storage">https://cloud.google.com/appengine/docs/standard/nodejs/using-cloud-storage</a></li>
</ul>
<h3 id="heading-deploy-new-changes-in-back-end">Deploy New Changes in Back-end</h3>
<p>I have a npm script in the root's <code>package.json</code> to publish new changes in my back-end to GCP:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"deploy-server"</span>: <span class="hljs-string">"gcloud app deploy ./server/app.yaml"</span>
}
</code></pre>
<p>Then run it in a terminal at your project's root:</p>
<pre><code class="lang-json">npm run deploy-server
</code></pre>
<h2 id="heading-hosting-your-spa">Hosting Your SPA</h2>
<p>When I was still on Lightsail, my SPA was <a target="_blank" href="https://medium.com/@kilgarenone/deploy-spa-to-aws-9302796acd88">hosted</a> on S3+Cloudfront because I assumed it's better to keep them under the same platform for better latency. Then I found GCP. </p>
<p>As a beat refugee from AWS landing in GCP, I first explored the 'Cloud Storage' to host my SPA, and turns out it wasn't ideal for SPA. It's rather convoluted. So you can skip that.</p>
<p>Then I tried hosting my SPA in <a target="_blank" href="https://firebase.google.com/docs/hosting/quickstart"><strong>Firebase</strong></a>. Easily done in minutes even when it was my first time there. I love it.</p>
<p>Another option you can consider is <a target="_blank" href="https://netlify.com">Netlify</a> which is super easy to get started too.</p>
<h3 id="heading-deploy-new-changes-in-front-end">Deploy New Changes in Front-end</h3>
<p>Similarly to deploying back-end changes, I have another npm script in the root's <code>package.json</code> to publish new changes in my front-end to Firebase:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"deploy-client"</span>: <span class="hljs-string">"npm run build-client &amp;&amp; firebase deploy"</span>,
    <span class="hljs-attr">"build-client"</span>: <span class="hljs-string">"npm run test &amp;&amp; cd client &amp;&amp; npm i &amp;&amp; npm run build"</span>,
    <span class="hljs-attr">"test"</span>: <span class="hljs-string">"npm run lint"</span>,
    <span class="hljs-attr">"lint"</span>: <span class="hljs-string">"npm run lint:js &amp;&amp; npm run lint:css"</span>,
    <span class="hljs-attr">"lint:js"</span>: <span class="hljs-string">"eslint 'client/src/**/*.js' --fix"</span>,
    <span class="hljs-attr">"lint:css"</span>: <span class="hljs-string">"stylelint '**/*.{scss,css}' '!client/dist/**/*'"</span>
}
</code></pre>
<p><em>"Whoa hold on, where all that stuff come from??"</em></p>
<p>They are a chain of scripts that each runs sequentially upon triggered by the <code>deploy-client</code> script. The <code>&amp;&amp;</code> character is what glues them together.</p>
<p>Let's hold each other's hands and walk through it from start to finish:</p>
<ol>
<li>First, we do <code>npm run deploy-client</code>,</li>
<li>which runs <code>build-client</code> first,</li>
<li>which runs <code>test</code> first, (see, we are just following where a script and its <code>&amp;&amp;</code> lead us, which is why <code>firebase deploy</code> won't run just yet)</li>
<li>which runs <code>lint</code>,</li>
<li>which brings us to <code>lint:js</code> first, and next, <code>lint:css</code>,</li>
<li>then back to <code>cd client</code>, followed by <code>npm i</code> and <code>npm run build</code>,</li>
<li>and finally, it's <code>firebase deploy</code>'s turn to run.</li>
</ol>
<p><strong>Tip</strong>: If the changes you made are full-stack, you could have a script that deploys 'client' and 'server' together:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"deploy-all"</span>: <span class="hljs-string">"npm run deploy-server &amp;&amp; npm run deploy-client"</span>,
}
</code></pre>
<h2 id="heading-rich-text-editor">Rich-text Editor</h2>
<p>Building the rich-text editor in Sametable was the second most challenging thing for me. I realized that I could have had it easy with those drop-in editors such as CKEditor and TinyMCE, but I wanted to be able to craft the writing experience in the editor, and nothing can do that better than <a target="_blank" href="https://prosemirror.net/"><strong>ProseMirror</strong></a>. Sure, I had other options too, which I decided against for several reasons:</p>
<ol>
<li><a target="_blank" href="https://quilljs.com/">Quilljs</a><ul>
<li>Seemed many unaddressed <a target="_blank" href="https://github.com/quilljs/quill/issues">issues</a>.</li>
<li>Shaped specifically by a special-interest group.</li>
<li>Involves hacky workaround once you ventured out from the set of standard use cases.</li>
</ul>
</li>
<li><a target="_blank" href="https://draftjs.org/">Draftjs</a><ul>
<li>They are tightly coupled with React.</li>
<li>With the overhead of virtual DOM, they won't perform as well as Prosemirror.</li>
</ul>
</li>
<li><a target="_blank" href="https://trix-editor.org/">trix</a><ul>
<li>Based on Web Component. I had issues integrating it in Preact.</li>
<li>It wasn't flexible to build a customized editing experience.</li>
</ul>
</li>
</ol>
<p><strong>Prosemirror</strong> is undoubtedly an <em>impeccable</em> library. But learning it was not for the faint of heart as far as I'm concerned. </p>
<p>I failed to build any encompassing mental models of it even after I'd read the <a target="_blank" href="https://prosemirror.net/docs/guide/">guide</a> several times. The only way I could make progress from there was by cross-referencing existing code examples and the <a target="_blank" href="https://prosemirror.net/docs/ref/">manual</a>, and trial-and-error from there. And if I exhausted that too, then I would ask in the forum and it's always answered. I wouldn't bother with StackOverflow unless maybe for the popular Quilljs.</p>
<p>These were the places I went scrounging for code samples:</p>
<ul>
<li>The <a target="_blank" href="https://prosemirror.net/examples/">official examples</a></li>
<li>Search the <a target="_blank" href="https://discuss.prosemirror.net/">forum</a></li>
<li>'Fork' <a target="_blank" href="https://github.com/prosemirror/prosemirror-example-setup">prosemirror-example-setup</a></li>
<li>An editor called <a target="_blank" href="https://tiptap.scrumpy.io/">tiptap</a> that's based on Prosemirror but built for Vuejs. The codebase actually has very few bits of Vuejs. So you can find lots of helpful Prosemirror-specific snippets there(thanks guys!).</li>
</ul>
<p>In keeping with the spirit of this learning journey, I have extracted the rich-text editor of Sametable in a CodeSandBox:</p>
<p><a target="_blank" href="https://codesandbox.io/s/compassionate-montalcini-gcgwc">https://codesandbox.io/s/compassionate-montalcini-gcgwc</a></p>
<p>?</p>
<p>(<strong>Note</strong>: Prosemirror is framework-agnostic; the CodeSandBox demo only uses 'create-react-app' for bundling ES6 modules.)</p>
<h2 id="heading-cors">CORS</h2>
<p>To stop your browser from complaining about CORS issues, it's <a target="_blank" href="https://medium.com/@kilgarenone/deploy-your-nodejs-app-to-digital-ocean-1de40797666f#4aa4">all about</a> getting your backend to send those <code>Access-Control-Allow-*</code> headers back per request. (Apologies for oversimplification is in order)</p>
<p>But, correct me if I'm wrong, there's <a target="_blank" href="https://stackoverflow.com/a/60502433/73323">no way</a> to configure CORS in GAE itself. So I had to do it with the <a target="_blank" href="https://www.npmjs.com/package/cors">cors</a> npm package:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> app = express();
<span class="hljs-keyword">const</span> cors = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cors"</span>);

<span class="hljs-keyword">const</span> ALLOWED_ORIGINS = [
  <span class="hljs-string">"http://localhost:8008"</span>,
  <span class="hljs-string">"https://web.sametable.app"</span>, <span class="hljs-comment">// your SPA's domain</span>
];

app.use(
  cors({
    <span class="hljs-attr">credentials</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// include Access-Control-Allow-Credentials: true. remember set xhr.withCredentials = true;</span>
    origin(origin, callback) {
      <span class="hljs-comment">// allow requests with no origin</span>
      <span class="hljs-comment">// (like mobile apps or curl requests)</span>
      <span class="hljs-keyword">if</span> (!origin) <span class="hljs-keyword">return</span> callback(<span class="hljs-literal">null</span>, <span class="hljs-literal">true</span>);
      <span class="hljs-keyword">if</span> (ALLOWED_ORIGINS.indexOf(origin) === <span class="hljs-number">-1</span>) {
        <span class="hljs-keyword">const</span> msg =
          <span class="hljs-string">"The CORS policy for this site does not "</span> +
          <span class="hljs-string">"allow access from the specified Origin."</span>;
        <span class="hljs-keyword">return</span> callback(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(msg), <span class="hljs-literal">false</span>);
      }
      <span class="hljs-keyword">return</span> callback(<span class="hljs-literal">null</span>, <span class="hljs-literal">true</span>);
    },
  })
);
</code></pre>
<h2 id="heading-payment-amp-subscription">Payment &amp; Subscription</h2>
<p>A SaaS usually allows users to pay and subscribe to access the paid features you have designated.</p>
<p>To enable that possibility in Sametable, I use <strong>Stripe</strong> to handle both the payment and subscription flows.</p>
<h3 id="heading-guide-6">Guide</h3>
<p>There are two ways to implement them:</p>
<ol>
<li><a target="_blank" href="https://stripe.com/docs/billing/subscriptions/fixed-price">Very hands-on</a> that's great for customizing your UI.</li>
<li><a target="_blank" href="https://stripe.com/docs/payments/checkout/set-up-a-subscription"><strong>Checkout</strong></a>. Fastest to implement. This was what I did.</li>
</ol>
<h3 id="heading-webhook">Webhook</h3>
<p>The last key component I needed for this piece was a 'webhook' which is basically just a typical endpoint in your Nodejs that can be called by a third-party such as Stripe.</p>
<p>I created a webhook that will be called when a payment has been charged successfully to signify in the user record that corresponds to the payee as a PRO user in Sametable from there onwards:</p>
<pre><code class="lang-javascript">router.post(
  <span class="hljs-string">"/webhook/payment_success"</span>,
  bodyParser.raw({ <span class="hljs-attr">type</span>: <span class="hljs-string">"application/json"</span> }),
  asyncHandler(<span class="hljs-keyword">async</span> (req, res, next) =&gt; {
    <span class="hljs-keyword">const</span> sig = req.headers[<span class="hljs-string">"stripe-signature"</span>];

    <span class="hljs-keyword">let</span> event;

    <span class="hljs-keyword">try</span> {
      event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret);
    } <span class="hljs-keyword">catch</span> (err) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).send(<span class="hljs-string">`Webhook Error: <span class="hljs-subst">${err.message}</span>`</span>);
    }

    <span class="hljs-comment">// Handle the checkout.session.completed event</span>
    <span class="hljs-keyword">if</span> (event.type === <span class="hljs-string">"checkout.session.completed"</span>) {
      <span class="hljs-comment">// 'session' doc: https://stripe.com/docs/api/checkout/sessions/object</span>
      <span class="hljs-keyword">const</span> session = event.data.object;

      <span class="hljs-comment">// here you can query your database to, for example,</span>
      <span class="hljs-comment">// update a particular user/tenant's record</span>

      <span class="hljs-comment">// Return a res to acknowledge receipt of the event</span>
      res.json({ <span class="hljs-attr">received</span>: <span class="hljs-literal">true</span> });
    } <span class="hljs-keyword">else</span> {
      res.status(<span class="hljs-number">400</span>);
    }
  })
);
</code></pre>
<h4 id="heading-reference">Reference</h4>
<p>Here is a code snippet of a webhook:
<a target="_blank" href="https://stripe.com/docs/webhooks/signatures#verify-official-libraries">https://stripe.com/docs/webhooks/signatures#verify-official-libraries</a></p>
<h4 id="heading-guide-7">Guide</h4>
<ul>
<li><a target="_blank" href="https://stripe.com/docs/webhooks">https://stripe.com/docs/webhooks</a></li>
</ul>
<h2 id="heading-landing-page">Landing page</h2>
<h3 id="heading-building">Building</h3>
<p>I use <a target="_blank" href="https://www.11ty.dev/"><strong>Eleventy</strong></a> to build the landing page of Sametable. I <a target="_blank" href="https://twitter.com/devongovett/status/1222953655722110981">wouldn't</a> recommend Gatsby or Nextjs. They are overkill for this job.</p>
<p>I started with one of the <a target="_blank" href="https://www.11ty.dev/docs/starter/">starter projects</a> as I was impatient to get my page off the ground. But I struggled working in them. </p>
<p>Although Eleventy claims to be a simple SSG, there are actually quite a few concepts to grasp if you are new to <a target="_blank" href="https://www.staticgen.com/">static site generators</a>(SSG). Coupled with the tools introduced by the starter kits, things can get complex. So I decided to start from zero and take my time reading the doc from start to finish, slowly building up. Quiet and easy.</p>
<h4 id="heading-guides">Guides</h4>
<ul>
<li><strong>Long version</strong><ul>
<li><a target="_blank" href="https://tatianamac.com/posts/beginner-eleventy-tutorial-parti/">https://tatianamac.com/posts/beginner-eleventy-tutorial-parti/</a></li>
<li><a target="_blank" href="https://www.11ty.dev/docs/">https://www.11ty.dev/docs/</a></li>
</ul>
</li>
<li><strong>Short version</strong>: <a target="_blank" href="https://github.com/kilgarenone/personal-website">https://github.com/kilgarenone/personal-website</a> (the first website I built as my personal site while learning 11ty. It has a homepage and blog posts. Very few concepts introduced here. You could start with this 'starter project')</li>
</ul>
<h3 id="heading-hosting-1">Hosting</h3>
<p>I use <a target="_blank" href="https://www.netlify.com/"><strong>Netlify</strong></a> to host the landing page. There are also <a target="_blank" href="https://surge.sh/">surge.sh</a> and <a target="_blank" href="https://vercel.com">Vercel</a>. You will be fine here.</p>
<h2 id="heading-terms-and-conditions">Terms and Conditions</h2>
<p>T&amp;C makes your SaaS legit. As far as I know, here are your options to come up with them:</p>
<ol>
<li>Write your own <a target="_blank" href="https://pinboard.in/tos/">https://pinboard.in/tos/</a>.</li>
<li>Copy and paste others'. Change accordingly. Never easy in my experience.</li>
<li>Lawyer up.</li>
<li>Generate them in <a target="_blank" href="https://getterms.io/"><strong>getterms.io</strong></a>.</li>
</ol>
<h2 id="heading-marketing">Marketing</h2>
<p>There is no shortage of marketing posts saying it was a bad idea to "<em>Let the product speaks for itself</em>". Well, not unless you were trying to 'hack growth' to win the game.</p>
<p>The following is the trajectory of existence I have in mind for Sametable:</p>
<ol>
<li>Build something that purportedly solves a problem.</li>
<li>Do your SEO. Write the blog posts. Anyone who is affected by the problem that you have solved for will search for it, or know about it by word of mouth.</li>
<li>If it still didn't take off, well, chances are you weren't solving a huge real problem, or enough people have already solved it. In that case, just be grateful for whatever success that comes your way over the long haul.</li>
</ol>
<h3 id="heading-resources-2">Resources</h3>
<ul>
<li><a target="_blank" href="https://stripe.com/en-my/atlas/guides/starting-sales">https://stripe.com/en-my/atlas/guides/starting-sales</a></li>
<li><a target="_blank" href="https://github.com/LisaDziuba/Marketing-for-Engineers">https://www.coryzue.com/writing/seo-for-developers/</a></li>
</ul>
<h2 id="heading-well-being">Well-being</h2>
<p>It's easy to sit and get lost in our contemporary work. And we do that by accumulating debts from the future. One of the debts is our personal <strong>health</strong>.</p>
<p>Here is how I try to stay on top of my health debt:</p>
<ul>
<li><strong>Install</strong> <a target="_blank" href="https://workrave.org/"><strong>Workrave</strong></a>. You can set it to lock your screen after an interval has passed. Most importantly, it can show some exercises that you can perform behind your computer!</li>
<li>Get an adjustable <strong>standing desk</strong> if you can afford it. I got mine from IKEA.</li>
<li>Do <a target="_blank" href="https://www.youtube.com/watch?v=Kjhl-8yU6hI"><strong>burpees</strong></a>. Stretch those joints. Maintain good posture. <a target="_blank" href="https://www.youtube.com/watch?v=59MaNHq8UDo">Planking</a> helps.</li>
<li><strong>Meditate</strong> to stay sane. I'm using <a target="_blank" href="https://meditofoundation.org/">Medito</a>.</li>
</ul>
<p>??</p>
<p>Thanks for reading. Be sure to check out my own SaaS tool <strong>Sametable</strong> to <a target="_blank" href="https://www.sametable.app">manage your work in spreadsheets</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
