<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chris Castle</title>
    <description>The latest articles on DEV Community by Chris Castle (@crcastle).</description>
    <link>https://dev.to/crcastle</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F53380%2F80aeb2a9-b2d8-4c2d-b33d-3e9f049af8ba.jpg</url>
      <title>DEV Community: Chris Castle</title>
      <link>https://dev.to/crcastle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/crcastle"/>
    <language>en</language>
    <item>
      <title>Render, Remix, and Strapi: Let's Build a Productivity Tips App</title>
      <dc:creator>Chris Castle</dc:creator>
      <pubDate>Mon, 04 Apr 2022 19:55:43 +0000</pubDate>
      <link>https://dev.to/render/render-remix-and-strapi-lets-build-a-productivity-tips-app-2i3m</link>
      <guid>https://dev.to/render/render-remix-and-strapi-lets-build-a-productivity-tips-app-2i3m</guid>
      <description>&lt;h2&gt;
  
  
  What are Strapi and Remix?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://strapi.io/" rel="noopener noreferrer"&gt;Strapi&lt;/a&gt; is "the leading open-source headless CMS. It's 100% JavaScript, fully customizable, and developer-first." Think of it as an admin GUI to manage your content. That content could be blog posts or product images or videos. And Strapi automatically generates both REST and GraphQL APIs for the content. Render has been an especially popular place to deploy Strapi because it provides one of the quickest paths to get Strapi running in a production environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://remix.run" rel="noopener noreferrer"&gt;Remix&lt;/a&gt; is "a full stack web framework that lets you focus on the user interface...to deliver a fast, slick, and resilient user experience." It comes from some well-known developers from the React ecosystem and has been getting more and more attention since its open source debut at the end of 2021.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Strapi and Remix Together?
&lt;/h2&gt;

&lt;p&gt;Strapi and Remix are both popular and growing open source JavaScript projects. In fact, they topped the list of &lt;a href="https://risingstars.js.org/2021/en#section-nodejs-framework" rel="noopener noreferrer"&gt;Node.js Framework Rising Stars in 2021&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fze99c2hkobstuch1wtyf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fze99c2hkobstuch1wtyf.png" alt="A screenshot showing Strapi and Remix (among others) as 2021 Node.js Rising Stars." width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But more importantly, these two tools complement each other well. Strapi serves as an ideal back end for a Remix front-end. Strapi gives us many things we, as developers, would otherwise have to build ourselves: a clean abstraction on top of a database, a data admin GUI, and an API for the front-end to use. Remix builds upon the vast React ecosystem, letting you quickly create a front-end and scale it up as needed. It addresses a lot of the time-consuming complexity that has become a regular part of a React developer’s work.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Productivity Tips App
&lt;/h2&gt;

&lt;p&gt;The inspiration for this app came from Render Engineer &lt;a href="https://twitter.com/joshishantanu4" rel="noopener noreferrer"&gt;Shantanu Joshi&lt;/a&gt;: "Over time, everyone develops a Swiss army knife of tips, tricks, and hacks to boost productivity. At Render, we created a #productivity-tips Slack channel for anyone to share their best productivity boosters with everyone on the team."&lt;/p&gt;

&lt;p&gt;But Slack doesn't provide a great way to discover tips that others have shared in the channel. You can scroll back through the conversation history, but wouldn't it be nice if there was a web page with a simple list? Using Strapi and Remix, we're going to make a web app to catalog all of these tips and share them with others.&lt;/p&gt;

&lt;p&gt;Here's a visual overview of what we'll build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1p82lce3d2gm48wjgpr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1p82lce3d2gm48wjgpr.png" alt="A diagram showing the high-level architecture of our Productivity Tips app." width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here are the steps we’ll take to get there:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up your development machine&lt;/li&gt;
&lt;li&gt;Configure and get familiar with Strapi&lt;/li&gt;
&lt;li&gt;Build the front-end with Remix&lt;/li&gt;
&lt;li&gt;Deploy everything to Render&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Set Up Your Development Machine
&lt;/h3&gt;

&lt;p&gt;You’ll need &lt;a href="https://nodejs.org/en/download/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;, &lt;a href="https://classic.yarnpkg.com/lang/en/docs/install/" rel="noopener noreferrer"&gt;Yarn&lt;/a&gt;, &lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;git&lt;/a&gt;, a &lt;a href="https://render.com/register" rel="noopener noreferrer"&gt;Render account&lt;/a&gt;, and a &lt;a href="https://cloudinary.com/users/register/free" rel="noopener noreferrer"&gt;Cloudinary account&lt;/a&gt;. If you don't have them already, install the tools and create the accounts linked above (everything's free).&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure and Get Familiar with Strapi
&lt;/h3&gt;

&lt;p&gt;We'll start with an &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi" rel="noopener noreferrer"&gt;example repository&lt;/a&gt; that contains only a few changes from the &lt;a href="https://docs.strapi.io/developer-docs/latest/getting-started/quick-start.html" rel="noopener noreferrer"&gt;default Strapi project scaffolding&lt;/a&gt;. Following the instructions in the &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi#set-up-strapi" rel="noopener noreferrer"&gt;Set up Strapi section&lt;/a&gt; of the example repository's README, run Strapi locally to get comfortable with Strapi and understand the minor code changes below.&lt;/p&gt;

&lt;p&gt;Here are the changes I made to the Strapi scaffolding. The links will take you to the code for each change.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi/blob/main/config/env/production/database.js" rel="noopener noreferrer"&gt;PostgreSQL in production&lt;/a&gt; and &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi/blob/main/config/database.js" rel="noopener noreferrer"&gt;SQLite for local development&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi/blob/main/config/env/production/plugins.js" rel="noopener noreferrer"&gt;Cloudinary for media storage, resizing, and serving in production&lt;/a&gt;, and use the filesystem during local development. No code changes were necessary to use the local filesystem because that is the default built into Strapi.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi/blob/main/config/env/production/server.js" rel="noopener noreferrer"&gt;Tell Strapi what its public URL is&lt;/a&gt; when deployed in production.&lt;/li&gt;
&lt;li&gt;Create the &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi/tree/main/src/api" rel="noopener noreferrer"&gt;Tip content type&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feq7z5k1ukxezbzw06g8d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feq7z5k1ukxezbzw06g8d.png" alt="A screenshot of the Strapi Admin GUI Content Type builder." width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: &lt;em&gt;Strapi requires you to define Content Types–effectively, the data schema–in code. When you deploy your Strapi service to production, those Content Types are deployed along with the Strapi application code. However, content for each Content Type only lives within the environment in which it's created. You can read more about this in the &lt;a href="https://docs.strapi.io/user-docs/latest/content-types-builder/creating-new-content-type.html" rel="noopener noreferrer"&gt;Strapi documentation&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In our example, we've created a &lt;code&gt;Tip&lt;/code&gt; Content Type in development, and we will deploy that to production. However, any content we create in development won't be deployed to production, and any content we create in production won't be available in development.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once you've completed the instructions to set up Strapi, including adding some sample Tip content in the Strapi Admin GUI, we'll move on to building the Remix service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build the Front-End with Remix
&lt;/h3&gt;

&lt;p&gt;Strapi is mostly plug-and-play, requiring you to write code only when you want to customize its default functionality. On the other hand, building a Remix app requires us to write more code. We can start with a Remix scaffolding, but we still need to create the web page views and API requests to to show our productivity tip data. I've broken the process into five steps to guide you through this code. Each step is a separate git branch in the example repository, and the README for each branch explains the changes in that branch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4564n5pe48letk8jkj4l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4564n5pe48letk8jkj4l.png" alt="A screenshot of the branches labeled step one through five in the example Remix repository." width="638" height="736"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can either start with the &lt;code&gt;step-1&lt;/code&gt; branch and work your way through the changes in each branch or skip to &lt;code&gt;step-5&lt;/code&gt;, which contains the final example code that we'll deploy to Render. To start at &lt;code&gt;step-1&lt;/code&gt; and work through the branches, follow the &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-remix#readme" rel="noopener noreferrer"&gt;README instructions in each branch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To skip to &lt;code&gt;step-5&lt;/code&gt;, fork the &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-remix" rel="noopener noreferrer"&gt;example repository&lt;/a&gt;, &lt;code&gt;git clone&lt;/code&gt; it, switch to the &lt;code&gt;step-5&lt;/code&gt; branch with &lt;code&gt;git checkout step-5&lt;/code&gt;, and then install the necessary dependencies with &lt;code&gt;npm install&lt;/code&gt;. Now we can connect the Remix app to Strapi's REST API. To do that,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy the &lt;code&gt;.env.example&lt;/code&gt; file to &lt;code&gt;.env&lt;/code&gt;. This file provides our Remix app with two variables it needs to access the Strapi API: &lt;code&gt;STRAPI_URL_BASE&lt;/code&gt; and &lt;code&gt;STRAPI_API_TOKEN&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We need to ask Strapi to generate an API token for use in development. In the Strapi Admin GUI, go to Settings --&amp;gt; API Tokens --&amp;gt; Create new API Token. Name it something like &lt;code&gt;Remix Dev&lt;/code&gt; and leave it as &lt;code&gt;Read-only&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Copy the token and paste it into &lt;code&gt;.env&lt;/code&gt; as the value for &lt;code&gt;STRAPI_API_TOKEN&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Start or restart the Remix dev server by running &lt;code&gt;npm run dev&lt;/code&gt; in the Remix app directory, and also make sure the Strapi server is running by running &lt;code&gt;yarn develop&lt;/code&gt; in the Strapi app directory.&lt;/li&gt;
&lt;li&gt;If you open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; in a browser, you should see your Productivity Tips app containing any Tip data you've created in the Strapi Admin GUI!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnxhag6te394017najeu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnxhag6te394017najeu.png" alt="A screenshot of the Remix Productivity Tips app." width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nice job! Local development is done! Time to deploy our code to Render.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Everything to Render
&lt;/h3&gt;

&lt;p&gt;We will use &lt;a href="https://render.com/docs/infrastructure-as-code" rel="noopener noreferrer"&gt;Render Blueprints&lt;/a&gt; to make deploying and connecting these two services faster and less error-prone. A Render Blueprint is defined using a single &lt;code&gt;render.yaml&lt;/code&gt; file. &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi/blob/main/render.yaml" rel="noopener noreferrer"&gt;That file&lt;/a&gt; lives in the Strapi repository for these two applications.&lt;/p&gt;

&lt;p&gt;Follow the instructions in the &lt;a href="https://github.com/render-examples/strapiconf2022-workshop-strapi#deploy-to-production-on-render" rel="noopener noreferrer"&gt;Deploy to Production on Render&lt;/a&gt; section of the Strapi repository's README. You'll make minor changes to the &lt;code&gt;render.yaml&lt;/code&gt; file to make sure it deploys the code from your copies of the example repositories. Then you’ll perform the initial deploy, generate a production Strapi API token, and update the deployed production Remix service with that API token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpo0oj4vfguyu2mm3iede.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpo0oj4vfguyu2mm3iede.png" alt="A screenshot showing a successfully deployed Blueprint for the Productivity Tips app." width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you have your very own Productivity Tips app with which you and your coworkers can collect and share all your best productivity tips! Start adding tips using the Strapi Admin GUI, and then share the Remix service's URL with your team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customize
&lt;/h2&gt;

&lt;p&gt;I encourage you to extend the Remix and Strapi applications to customize them for your needs! Here are some ideas for inspiration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a search bar so your coworkers can search for tips about a specific topic or tool. Algolia’s &lt;a href="https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/react/" rel="noopener noreferrer"&gt;React InstantSearch&lt;/a&gt; library might be useful for this.&lt;/li&gt;
&lt;li&gt;Add a login page to restrict use of the Remix app to only approved users. Use OAuth with Google or GitHub login so you don’t have to write code to manage user accounts and password resets. Okta has guides for adding &lt;a href="https://developer.okta.com/docs/guides/add-an-external-idp/google/main/" rel="noopener noreferrer"&gt;Google&lt;/a&gt;, &lt;a href="https://developer.okta.com/docs/guides/social-login/github/main/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, and many other login providers to a web application. If you don’t want to use a 3rd party login provider, you could &lt;a href="https://strapi.io/blog/a-beginners-guide-to-authentication-and-authorization-in-strapi" rel="noopener noreferrer"&gt;store and manage users directly in Strapi&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Make the screenshot thumbnails for each productivity tip clickable and zoomable. Strapi’s Cloudinary plugin that we're using automatically creates small, medium, and large versions of images you attach to a Tip, but I didn’t have time to add more than the thumbnail view to the Remix app.&lt;/li&gt;
&lt;li&gt;Replace &lt;a href="https://picocss.com" rel="noopener noreferrer"&gt;Pico.css&lt;/a&gt; with a different &lt;a href="https://github.com/troxler/awesome-css-frameworks#general-purpose" rel="noopener noreferrer"&gt;minimal CSS theme&lt;/a&gt;, or even add your own styling to match your company or team’s brand colors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://twitter.com/crc" rel="noopener noreferrer"&gt;Let me know&lt;/a&gt; if you implement any of these! If they would be useful to others, I’ll consider merging them into the example repositories!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Remote Debugging with SSH and VS Code</title>
      <dc:creator>Chris Castle</dc:creator>
      <pubDate>Mon, 28 Feb 2022 19:48:15 +0000</pubDate>
      <link>https://dev.to/render/remote-debugging-with-ssh-and-vs-code-3ol8</link>
      <guid>https://dev.to/render/remote-debugging-with-ssh-and-vs-code-3ol8</guid>
      <description>&lt;p&gt;I was pretty excited when I discovered &lt;a href="https://en.wikipedia.org/wiki/Telnet" rel="noopener noreferrer"&gt;Telnet&lt;/a&gt; back in the 90s. I could access a command line on servers all over the college campus from my tiny dorm room! I no longer had to trudge through feet of snow and frigid temperatures (I went to college in the U.S. state of Maine) only to find all the computers in the lab occupied. I could connect to them from my room -- even if someone else was already using them!&lt;/p&gt;

&lt;p&gt;A more advanced version of me is now shocked at how pervasive the unencrypted Telnet protocol was in the 90s. Back then, I didn't even consider encrypting terminal traffic. Fortunately, others did. After discovering a password sniffer had been used on his university’s network, Finnish student &lt;a href="https://en.wikipedia.org/wiki/Secure_Shell#Version_1" rel="noopener noreferrer"&gt;Tatu Ylönen created the first version of the Secure Shell Protocol (or SSH)&lt;/a&gt; in 1995, and its use has grown continuously since.&lt;/p&gt;

&lt;p&gt;SSH is powerful because it can be used not only to get a command line on a remote computer but also to securely encapsulate almost any kind of data flowing between two machines. It can tunnel pixels from a remote machine's &lt;a href="https://en.wikipedia.org/wiki/X_Window_System" rel="noopener noreferrer"&gt;X11 server&lt;/a&gt;, sync files with &lt;a href="https://en.wikipedia.org/wiki/Rsync" rel="noopener noreferrer"&gt;rsync&lt;/a&gt;, and even mount a filesystem from a remote machine using &lt;a href="https://en.wikipedia.org/wiki/SSHFS" rel="noopener noreferrer"&gt;SSHFS&lt;/a&gt;. SSH has understandably become an essential tool in the software developer's toolkit.&lt;/p&gt;

&lt;p&gt;We recently &lt;a href="https://render.com/blog/winter-release-new-features#native-ssh-connectivity-for-remote-access" rel="noopener noreferrer"&gt;announced&lt;/a&gt; the ability to SSH into your Render services, so I was excited to see how I could use it to improve my development workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SSH?
&lt;/h2&gt;

&lt;p&gt;We primarily built SSH on Render to allow you to get a command line in containers running a service you’ve deployed. We don’t recommend using it in production regularly, but it can be helpful in a pinch, to run a database migration after a deployment or to check the contents of a file being written to disk.&lt;/p&gt;

&lt;p&gt;But there’s a lot more you can do with SSH. For example, you can &lt;a href="https://render.com/docs/disks#scp" rel="noopener noreferrer"&gt;use &lt;code&gt;scp&lt;/code&gt; to copy files&lt;/a&gt; to or from your service’s &lt;a href="https://render.com/docs/disks" rel="noopener noreferrer"&gt;persistent disk&lt;/a&gt;. For my own needs, I was curious if I could use Render SSH for port forwarding and eventually remote debugging.&lt;/p&gt;

&lt;p&gt;Some of the most frustrating bugs I’ve encountered are those I can’t reproduce locally. I get a bug report from a user along with instructions to reproduce it. I can reproduce the bug in production (running Linux) but surprisingly can’t reproduce it on my Mac. How do I investigate and fix a bug if I can’t reproduce it on my development machine?&lt;/p&gt;

&lt;h2&gt;
  
  
  Debug Node.js Remotely
&lt;/h2&gt;

&lt;p&gt;Remote debugging to the rescue! Here’s the code for a simplified Node.js HTTP server I will debug remotely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;127.0.0.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NODE_ENV is set to "production"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, World!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/exception&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Some code that causes an exception&lt;/span&gt;
      &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`An error occurred: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server running at http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a refresher, to attach a debugger to the &lt;code&gt;node&lt;/code&gt; process if it were running locally, I’d start &lt;code&gt;node&lt;/code&gt; with the &lt;code&gt;--inspect&lt;/code&gt; flag and then attach VS Code (or one of several other &lt;a href="https://nodejs.org/en/docs/guides/debugging-getting-started/#inspector-clients" rel="noopener noreferrer"&gt;debugger clients&lt;/a&gt;) to the process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;node &lt;span class="nt"&gt;--inspect&lt;/span&gt; server.js
Debugger listening on ws://127.0.0.1:9229/a9ca58f7-e847-4c31-9e49-82f10ee3a5f5
For &lt;span class="nb"&gt;help&lt;/span&gt;, see: https://nodejs.org/en/docs/inspector
Server running at http://127.0.0.1:10000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now VS Code would show me variable values and the call stack at any point during the program’s execution. &lt;/p&gt;

&lt;p&gt;But getting this working when the &lt;code&gt;node&lt;/code&gt; process is running on Render is not as straightforward. Render accepts traffic from the public internet for only one port for a Web Service. My Web Service is already listening on port 10000 for HTTP traffic, so I can’t expose the debug port publicly. Moreover, even when Render starts supporting traffic on multiple ports, &lt;a href="https://nodejs.org/en/docs/guides/debugging-getting-started/#exposing-the-debug-port-publicly-is-unsafe" rel="noopener noreferrer"&gt;exposing the debug port publicly is unsafe&lt;/a&gt;! Fortunately, the Node.js documentation provides &lt;a href="https://nodejs.org/en/docs/guides/debugging-getting-started/#enabling-remote-debugging-scenarios" rel="noopener noreferrer"&gt;guidance on enabling remote debugging safely&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Render
&lt;/h3&gt;

&lt;p&gt;The first step is to edit the &lt;strong&gt;Start Command&lt;/strong&gt; for the service from the Render Dashboard. Instead of running &lt;code&gt;node server.js&lt;/code&gt;, I have to run &lt;code&gt;node --inspect=9229 server.js&lt;/code&gt;. &lt;code&gt;9229&lt;/code&gt; is the default port the Node.js debugger will listen on, but I want to be explicit to avoid confusion. Then I &lt;a href="https://render.com/docs/ssh-keys" rel="noopener noreferrer"&gt;add an SSH key to my Render account&lt;/a&gt;. This only needs to be done once for each Render account.&lt;/p&gt;

&lt;p&gt;While the service restarts with the new start command, I’m going to configure VS Code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure VS Code
&lt;/h3&gt;

&lt;p&gt;VS Code requires a &lt;code&gt;launch.json&lt;/code&gt; configuration file to connect to the &lt;code&gt;node&lt;/code&gt; process as a debug client. To create this, open &lt;code&gt;.vscode/launch.json&lt;/code&gt; (create this folder and file if needed) and click &lt;strong&gt;Add Configuration…&lt;/strong&gt; in the bottom right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Felglp752eb7wbavqasmr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Felglp752eb7wbavqasmr.png" alt="A screenshot of configuring VS Code to remotely debug a Node.js process." width="800" height="494"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    Configuring VS Code to remotely debug a Node.js process&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;Select &lt;strong&gt;Node.js: Attach to Remote Program&lt;/strong&gt;, and VS Code will generate a template with some values that need to be filled in. The &lt;code&gt;address&lt;/code&gt; and &lt;code&gt;port&lt;/code&gt; are where VS Code will try to connect to the &lt;code&gt;node&lt;/code&gt; process, and the &lt;code&gt;remoteRoot&lt;/code&gt; is the directory to which code is deployed on Render. This path won’t change if you’re using the native Render Node environment instead of Docker. Here is the &lt;code&gt;launch.json&lt;/code&gt; I used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"configurations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"localRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Attach to Remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"remoteRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/opt/render/project/src"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"attach"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"skipFiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;node_internals&amp;gt;/**"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pwa-node"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create the SSH Tunnel
&lt;/h3&gt;

&lt;p&gt;Now that the Web Service is using the new &lt;strong&gt;Start Command&lt;/strong&gt;, I can create an SSH tunnel between the &lt;code&gt;node&lt;/code&gt; process running on Render and my local development machine. First I grab the &lt;code&gt;ssh&lt;/code&gt; command for my Web Service from the Render Dahsboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpusyet830hhefvi3ypar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpusyet830hhefvi3ypar.png" alt="A screenshot of the Render Dashboard showing the SSH connection command for a Web Service." width="800" height="469"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    The &lt;code&gt;ssh&lt;/code&gt; command for a Web Service&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;I need to modify it so that &lt;code&gt;ssh&lt;/code&gt; creates a tunnel between two ports instead of giving me a command line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; 9229:localhost:9229 srv-c804oa46fj3d2u40bpq0@ssh.oregon.render.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;-N&lt;/code&gt; flag tells &lt;code&gt;ssh&lt;/code&gt; to not execute a remote command after establishing the connection. Normally when you &lt;code&gt;ssh&lt;/code&gt; into a machine, a command like &lt;code&gt;bash&lt;/code&gt;&lt;sup id="fnref1"&gt;1&lt;/sup&gt; is run. I don’t need that because I only want &lt;code&gt;ssh&lt;/code&gt; to forward TCP traffic back and forth. &lt;/li&gt;
&lt;li&gt;The &lt;code&gt;-L&lt;/code&gt; flag instructs &lt;code&gt;ssh&lt;/code&gt; to create a tunnel for TCP traffic such that connections to port &lt;code&gt;9229&lt;/code&gt; on my local machine are forwarded to &lt;code&gt;localhost:9229&lt;/code&gt; on the remote machine.&lt;/li&gt;
&lt;li&gt;Finally, &lt;code&gt;srv-c804oa46fj3d2u40bpq0@ssh.oregon.render.com&lt;/code&gt; is the username and domain name &lt;code&gt;ssh&lt;/code&gt; will use to create the connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Start Debugging
&lt;/h3&gt;

&lt;p&gt;Now I can start the debugging session! In VS Code I’ve opened the project deployed to Render, switched the left side panel to &lt;strong&gt;Run and Debug&lt;/strong&gt;, and then clicked the play button at the top to start debugging.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foyjtn06lkvkzbwm581tm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foyjtn06lkvkzbwm581tm.png" alt="A screenshot showing VS Code's initial state when it connects as a debugging client." width="800" height="484"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    VS Code connected as a debug client to a remote &lt;code&gt;node&lt;/code&gt; process&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;Let’s walk through everything I can see and do now. On the bottom, a &lt;strong&gt;Debug Console&lt;/strong&gt; appeared, which now shows me the logs from my service (stdout and stderr). On the left within the &lt;strong&gt;Loaded Scripts&lt;/strong&gt; panel, I select the &lt;code&gt;server.js&lt;/code&gt; file so that I can add breakpoints and see which line program execution is on.&lt;/p&gt;

&lt;p&gt;To confirm that remote debugging is working the same way local debugging does in VS Code, I add a breakpoint on line 15.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zlkcavfubxbxan8r1j9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zlkcavfubxbxan8r1j9.png" alt="A screenshot of VS Code showing a breakpoint on line 15." width="800" height="89"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    Add a breakpoint to line 15&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;This is part of the code that is executed when a request to &lt;code&gt;/&lt;/code&gt; is made. I make a request to that path in a new browser tab, and it looks like everything is working as expected! I can see that execution paused on line 15, and VS Code shows me the state of the &lt;code&gt;req&lt;/code&gt; and &lt;code&gt;res&lt;/code&gt; local variables and the call stack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvwa91f62zdytg7g3vb6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvwa91f62zdytg7g3vb6.png" alt="A screenshot of VS Code pausing program execution at a breakpoint." width="800" height="484"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    Program execution paused at the breakpoint&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;Earlier in this post, I said that remote debugging is helpful to investigate an issue observed in production but not on my local development machine. Let’s say the bug causes an exception, but I don’t know which line of code is generating the exception. I can tell VS Code to automatically pause program execution when it hits both caught and uncaught exceptions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5sve6pmdyx0m4f1tr0ux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5sve6pmdyx0m4f1tr0ux.png" alt="A screenshot of VS Code configured to pause execution on all exceptions." width="800" height="473"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    Break on caught and uncaught exceptions&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;To test this out, I make a request to &lt;code&gt;/exception&lt;/code&gt;. VS Code automatically pauses execution on the line that generates the exception. This example is contrived because I purposely wrote this line to generate an exception. Still, it’s a simple demonstration of how you can remotely debug exceptions before you have to handle a real-life (and more complex) scenario.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkdzyvp5dp6cvbyflxym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkdzyvp5dp6cvbyflxym.png" alt="A screenshot of VS Code pausing program execution on a caught exception." width="800" height="484"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    VS Code pausing program execution on a caught exception&lt;br&gt;
  
  &lt;/p&gt;

&lt;h2&gt;
  
  
  More to Explore
&lt;/h2&gt;

&lt;p&gt;VS Code’s remote debugging feature can also help if you need to investigate CPU or memory use. The &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-js-profile-flame" rel="noopener noreferrer"&gt;Flame Chart Visualizer for JavaScript extension&lt;/a&gt; provides a real-time chart of CPU and memory use of the &lt;code&gt;node&lt;/code&gt; process and a flame chart to help you identify CPU hotspots in your code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpv728imei4qotzis5bax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpv728imei4qotzis5bax.png" alt="An example flame chart showing CPU use chronologically." width="800" height="281"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    Example flame chart&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;And you can use VS Code to debug more than just Node.js remotely: use it to connect to an Elixir cluster, a Go process, a Ruby process, and much more. There are VS Code extensions available for all popular languages that enable remote debugging for that language.&lt;/p&gt;

&lt;p&gt;What are you going to use Render SSH for? Let me know in a comment below!&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;The shell that’s run is configured per user on the remote machine. You can also override the command by appending a command to the end of the &lt;code&gt;ssh&lt;/code&gt; command. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>vscode</category>
      <category>tooling</category>
      <category>debugging</category>
      <category>ssh</category>
    </item>
    <item>
      <title>Add Password Protection to Any Site with OAuth2 Proxy - Plus Social Logins</title>
      <dc:creator>Chris Castle</dc:creator>
      <pubDate>Mon, 14 Feb 2022 19:51:52 +0000</pubDate>
      <link>https://dev.to/render/add-password-protection-to-any-site-with-oauth2-proxy-plus-social-logins-223c</link>
      <guid>https://dev.to/render/add-password-protection-to-any-site-with-oauth2-proxy-plus-social-logins-223c</guid>
      <description>&lt;p&gt;Continuing on the topic of adding password protection to your site, let’s look at using a reverse proxy server to password protect a service you have deployed to Render. The &lt;a href="https://dev.to/render/password-protect-static-sites-with-pagecrypt-4dia"&gt;previous post&lt;/a&gt; on this topic covered adding password protection to a static site using PageCrypt. Now we’re expanding beyond static sites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What’s the problem we’re trying to solve?&lt;/strong&gt; Say you have an existing Ruby, Node.js, or Python web service (it could be one of our native environments or a Dockerized app), and you want to protect it with password authentication. Maybe you don’t want to touch the existing service’s code because it works well, and you’re concerned about inadvertently introducing a bug. Or perhaps you have multiple services to protect, and you don’t have time to implement password protection for each of them.&lt;/p&gt;

&lt;p&gt;An interesting way to solve this problem is to put a reverse proxy in front of your existing web service(s).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What’s a reverse proxy server?&lt;/strong&gt; It’s a server that acts as a gate and traffic cop guiding incoming traffic to the service that can respond to it. Typically that service is not exposed to the public internet. The scenario we’ll look at below shuttles web traffic between the public internet and a Render &lt;a href="https://render.com/docs/private-services" rel="noopener noreferrer"&gt;Private Service&lt;/a&gt;, which is protected from the public internet and only accessible to applications you own. NGINX and Apache are examples of general-purpose web servers that can also be used as reverse proxies, but there are many purpose-built ones like &lt;a href="https://traefik.io/" rel="noopener noreferrer"&gt;Træfɪk&lt;/a&gt; and &lt;a href="https://caddyserver.com/" rel="noopener noreferrer"&gt;Caddy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Reverse proxies are commonly used for load balancing or TLS termination but Render already provides these features to you out of the box. In our scenario, we will deploy a reverse proxy specifically for authentication and authorization.&lt;/p&gt;

&lt;p&gt;The reverse proxy we’re going to focus on in this post is called &lt;a href="https://github.com/oauth2-proxy/oauth2-proxy" rel="noopener noreferrer"&gt;oauth2-proxy&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is it good for?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Adding social login to your site – i.e., login managed by another service like GitHub, Google, or one of the other fourteen supported OAuth providers&lt;/li&gt;
&lt;li&gt;Cases in which you can’t or don’t want to change the code of the web service you want to protect&lt;/li&gt;
&lt;li&gt;Maintaining clear separation between auth code and your web service code&lt;/li&gt;
&lt;li&gt;Language flexibility: your web service doesn’t need to be written in the same language as oauth2-proxy (which is written in Go).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What are the drawbacks?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Deployed as an extra service, which has a cost (but you can use Render’s free plan)&lt;/li&gt;
&lt;li&gt;Configuration of OAuth can be confusing, but the oauth2-proxy documentation explains it well&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What is it, and how does it work?
&lt;/h3&gt;

&lt;p&gt;A coworker shared oauth2-proxy as part of a &lt;a href="https://dev.to/render/use-one-click-deploys-for-secure-online-vs-code-3mdh"&gt;previous Render blog post&lt;/a&gt;, and I think it’s pretty cool. It does two things. First, it authenticates a user with an OAuth 2.0 flow. Then, after successful authentication, it acts as a reverse proxy, forwarding web requests to a Private Service behind it. The Private Service shouldn’t be exposed to the public internet, otherwise, the user will be able to access it without authenticating.&lt;/p&gt;

&lt;p&gt;Are you wondering what OAuth 2.0 is, or do you need a refresher on it? (If not, you can skip to the diagram.) &lt;a href="https://oauth.net/2/" rel="noopener noreferrer"&gt;OAuth 2.0&lt;/a&gt; is a standard defined by the IETF OAuth Working Group. It’s a ubiquitous authorization method that lets you delegate authentication and often authorization to another trusted service. For example, you can use Google to log in to the Render Dashboard.​​&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcd2lukbh3wdni6c3be4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcd2lukbh3wdni6c3be4.png" alt="Render's login page" width="800" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OAuth 2.0 specifies a multi-step, production-hardened flow we can follow to allow Google to authenticate a user’s credentials and securely tell us that the user is, in fact, the owner of a specific email address, say, &lt;a href="mailto:user@example.com"&gt;user@example.com&lt;/a&gt;.&lt;sup id="fnref1"&gt;1&lt;/sup&gt; Armed with the fact that we confidently know this is &lt;a href="mailto:user@example.com"&gt;user@example.com&lt;/a&gt; (and not &lt;a href="mailto:sneakyblackhat@example.com"&gt;sneakyblackhat@example.com&lt;/a&gt; pretending to be &lt;a href="mailto:user@example.com"&gt;user@example.com&lt;/a&gt;), we can then show the user the resources they have access to — e.g., a web page, some data, or their daily New York Times crossword puzzle.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick detour&lt;/strong&gt;: You may have noticed I’ve used two different words that begin with &lt;em&gt;auth&lt;/em&gt;: authentication and authorization. People often confuse them or mean both when they say one. So what’s the difference? &lt;strong&gt;Authentication&lt;/strong&gt; verifies that a user is who they say they are. &lt;strong&gt;Authorization&lt;/strong&gt; determines the user’s permissions within your app. In the example above, if a user logs in to Render with Google credentials, Google handles the authentication, assuring us the user is who they say they are. After the user is authenticated, Render needs to decide what services and teams to show them. We look up the services and teams they are authorized to manage — those created by them or shared with them by another Render user.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Traffic Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps6em6nzd2oy0wu4kog9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps6em6nzd2oy0wu4kog9.png" alt="oauth2-proxy traffic flow" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So how does oauth2-proxy work? Let’s look at the flow from the perspective of a user trying to access your app. Note that this flow is specific to oauth2-proxy, not a generic OAuth 2.0 flow.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The browser makes an HTTP request to your site, which is directed to the oauth2-proxy service.&lt;/li&gt;
&lt;li&gt;oauth2-proxy responds, prompting the user to authenticate themselves with an OAuth 2.0 provider you’ve configured. In this example, we’ll assume it’s Google.&lt;sup id="fnref2"&gt;2&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;The browser is redirected to a Google login page.&lt;/li&gt;
&lt;li&gt;The user enters their credentials for Google to verify.&lt;/li&gt;
&lt;li&gt;Upon successful authentication, Google redirects the user back to your oauth2-proxy service along with a unique token — a query string parameter that Google refers to as an ID token.&lt;/li&gt;
&lt;li&gt;oauth2-proxy makes an HTTP request to Google containing the ID token along with a client ID and client secret that Google has uniquely assigned to your instance of oauth2-proxy. This step is a necessary part of the OAuth flow. A malicious user could spoof the request with a made-up ID token. This out-of-band request between oauth2-proxy and Google verifies the ID token was generated by Google from a recent login for this user.&lt;/li&gt;
&lt;li&gt;Google responds, verifying the validity of those three values and finally confirming to oauth2-proxy the user is who they say they are!&lt;/li&gt;
&lt;li&gt;By default, oauth2-proxy now authorizes all traffic from this user passing it to your Private Service. However, you can &lt;a href="https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview/#config-file" rel="noopener noreferrer"&gt;configure&lt;/a&gt; oauth2-proxy’s authorization rules in several ways. You can restrict access based on membership in a Google Group, GitHub org, or GitLab project. oauth2-proxy can also pass the username to the Private Service which can implement its own authorization logic.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;I forked the oauth2-proxy repository and made a few &lt;a href="https://github.com/render-examples/oauth2-proxy-blog/compare/4f5efd4..e048670" rel="noopener noreferrer"&gt;changes&lt;/a&gt; so that you can deploy a working example to Render for free.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frender.com%2Fimages%2Fdeploy-to-render-button.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frender.com%2Fimages%2Fdeploy-to-render-button.svg" alt="Click to deploy an example oauth2-proxy service to Render for free" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before the deployment starts, you’ll be asked for three values: an OAuth provider, client ID, and secret. The &lt;a href="https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/" rel="noopener noreferrer"&gt;oauth2-proxy documentation&lt;/a&gt; explains how to generate a client ID and client secret using any one of fourteen supported OAuth providers.&lt;/p&gt;

&lt;p&gt;I’ve chosen Google as the OAuth provider for an example deployment: &lt;a href="https://oauth2-proxy-yuao.onrender.com/oauth2/sign_in" rel="noopener noreferrer"&gt;try it out&lt;/a&gt;. I'm using oauth2-proxy's default login screen, but you can customize its design. You’ll be prompted to &lt;strong&gt;Sign in with Google&lt;/strong&gt;. After Google authenticates your credentials (which aren’t shared with me or Render), your requests will be proxied to a &lt;a href="https://github.com/render-examples/express-hello-world" rel="noopener noreferrer"&gt;Node.js service&lt;/a&gt; deployed to Render as a &lt;a href="https://render.com/docs/private-services" rel="noopener noreferrer"&gt;Private Service&lt;/a&gt;. Private Services on Render are protected from the public internet and only accessible to applications you own. The oauth2-proxy service receives a request from your browser, passes it to the Node.js service, and then passes the Node.js service’s response back to your browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  More to Explore
&lt;/h3&gt;

&lt;p&gt;If oauth2-proxy doesn't suit your needs, there are some projects that have spun-off from oauth2-proxy like &lt;a href="https://github.com/pomerium/pomerium" rel="noopener noreferrer"&gt;pomerium&lt;/a&gt; and BuzzFeed's &lt;a href="https://github.com/buzzfeed/sso" rel="noopener noreferrer"&gt;sso&lt;/a&gt;. In addition to the open source library, Pomerium offers a paid service with a GUI to help IT staff more easily manage user permissions. BuzzFeed's sso builds upon oauth2-proxy by separating the domain used for auth from the domain used for the proxy (among several other changes).&lt;/p&gt;

&lt;p&gt;If you're interestd in learning more about OAuth, there is &lt;a href="https://oauth.net/3/" rel="noopener noreferrer"&gt;ongoing work to evolve the OAuth 2.0 standard&lt;/a&gt;. &lt;a href="https://oauth.net/2.1/" rel="noopener noreferrer"&gt;OAuth 2.1&lt;/a&gt; exists in a draft state, and &lt;a href="https://oauth.net/gnap/" rel="noopener noreferrer"&gt;GNAP&lt;/a&gt; is a new IETF Working Group developing "a next-generation protocol that encompasses many use cases that are challenging to implement with OAuth 2.0."&lt;/p&gt;

&lt;p&gt;Are you using oauth2-proxy or another reverse proxy with password protection? &lt;a href="https://twitter.com/crc" rel="noopener noreferrer"&gt;Let me know&lt;/a&gt; what worked and didn't work for you! I'm curious to learn more, and I'm sure others will find it helpful to hear about your experience.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;It doesn't necessarily have to be an email. It could be a username or a user ID or any other identifier. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;You could even &lt;a href="https://github.com/oauthjs/node-oauth2-server" rel="noopener noreferrer"&gt;deploy your own&lt;/a&gt;! ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Password Protect Static Sites with PageCrypt</title>
      <dc:creator>Chris Castle</dc:creator>
      <pubDate>Wed, 02 Feb 2022 20:25:53 +0000</pubDate>
      <link>https://dev.to/render/password-protect-static-sites-with-pagecrypt-4dia</link>
      <guid>https://dev.to/render/password-protect-static-sites-with-pagecrypt-4dia</guid>
      <description>&lt;p&gt;Password protecting static sites is tricky. You could use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt" rel="noopener noreferrer"&gt;&lt;code&gt;window.prompt()&lt;/code&gt;&lt;/a&gt; to ask a site visitor to enter a password before the site content is revealed, but a resourceful visitor will quickly get around this with right-click, view source (or &lt;code&gt;curl&lt;/code&gt; or numerous other ways). All of the site's HTML, CSS, and JavaScript are served to the web browser, and they constitute the entirety of the site.&lt;/p&gt;

&lt;p&gt;If you are using a backend API that the static site pulls data from (like many single-page apps do these days), you could require authentication for API requests, but this doesn't protect the HTML, CSS, and JavaScript.&lt;/p&gt;

&lt;p&gt;In trying to find a good solution for this for Render users, we discovered PageCrypt. It's a free, open source library that allows you to password protect these static assets securely. Let's investigate how PageCrypt works and test it out!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's it good for?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Protecting a &lt;a href="https://render.com/docs/static-sites" rel="noopener noreferrer"&gt;static site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cases where you don't need (or can't run) backend code&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What are the drawbacks?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Only encrypts a single HTML file by default&lt;/li&gt;
&lt;li&gt;Only supports a single shared password (no per-user passwords)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is it, and how does it work?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Greenheart/pagecrypt" rel="noopener noreferrer"&gt;PageCrypt&lt;/a&gt; is a novel solution to password protecting HTML without a backend. It’s a library you can use as part of your site’s build step or as a command line tool. It uses the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API" rel="noopener noreferrer"&gt;Web Crypto API&lt;/a&gt; -- currently &lt;a href="https://caniuse.com/cryptography" rel="noopener noreferrer"&gt;supported by all major browsers&lt;/a&gt; -- and a password to encrypt an HTML page, which you can then host on any static hosting platform, including Render! An HTML page encrypted with PageCrypt prompts the viewer for a password. Upon entering the correct password, the page is decrypted and its content replaces the password prompt.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
    &lt;br&gt;
  &lt;br&gt;Example static site password protected with PageCrypt
  &lt;/p&gt;

&lt;p&gt;One potential concern with PageCrypt is that it only encrypts an HTML file by default. If you want to encrypt your CSS and JavaScript files, you’ll have to inline them in the HTML file. The same applies to images and any other binary assets; you’ll have to inline them as &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs" rel="noopener noreferrer"&gt;Data URIs&lt;/a&gt;. As with any authentication and authorization solution, you’ll want to determine what’s acceptable for your security requirements. Maybe you’re comfortable with the risk of images leaking but have higher security requirements for your JavaScript. In that case, the HTML page can link to the image files but should contain all your JavaScript. You can use many static site build tools to automate inlining assets in HTML. &lt;a href="https://webpack.js.org" rel="noopener noreferrer"&gt;Webpack&lt;/a&gt;, &lt;a href="https://gulpjs.com" rel="noopener noreferrer"&gt;Gulp&lt;/a&gt;, or &lt;a href="https://gruntjs.com" rel="noopener noreferrer"&gt;Grunt&lt;/a&gt; are just a few that might be useful.&lt;/p&gt;

&lt;p&gt;PageCrypt also doesn't allow users to have individual usernames and passwords. It only works with a single, shared password. If you need the more fine-grained control provided by user accounts, check out a service like &lt;a href="https://auth0.com" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;p&gt;Adding PageCrypt to the build step of a &lt;em&gt;Hello World&lt;/em&gt; static site was straightforward. The instructions in the &lt;a href="https://github.com/Greenheart/pagecrypt#readme" rel="noopener noreferrer"&gt;project’s README&lt;/a&gt; provide clear guidance on how to use PageCrypt as a library with browser-executed JavaScript, Node.js, or Deno, and how to use it as a CLI tool. I used the CLI in the build step of my example static site.&lt;/p&gt;

&lt;p&gt;Add PageCrypt to your project as a dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;yarn add pagecrypt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update the build step in &lt;code&gt;package.json&lt;/code&gt; to use the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&amp;lt;snip&amp;gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pagecrypt src/index.html dist/index.html $PASSWORD &amp;amp;&amp;amp; cp -R src/css dist/"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&amp;lt;snip&amp;gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The password is set using the &lt;code&gt;$PASSWORD&lt;/code&gt; environment variable since we don’t want to store that in the code. Using an environment variable also allows you to change the password and rebuild the site quickly.&lt;/p&gt;

&lt;p&gt;Here’s an &lt;a href="https://pagecrypt.onrender.com/" rel="noopener noreferrer"&gt;example deployment&lt;/a&gt; of the site. The password is &lt;code&gt;s3cr3t&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To get a deeper understanding of how PageCrypt works, try entering an incorrect password. Then right-click and view the source of the page. PageCrypt generated the contents of this page during the build step. Your original page content is encrypted inside a hidden &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; element at the bottom of the HTML document.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7mj0dz6200xsf5g5fy4v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7mj0dz6200xsf5g5fy4v.png" alt="A screenshot of a PageCrypt-encrypted HTML page in Google Chrome's elements inspector." width="800" height="178"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
    Encrypted HTML page inside the &amp;lt;pre&amp;gt; element.&lt;br&gt;
  
  &lt;/p&gt;

&lt;p&gt;After the correct password is entered, your page content is decrypted and shown.&lt;/p&gt;

&lt;p&gt;To make it easier for users to access password protected pages, PageCrypt also allows you to create a "magic link" that decrypts the page without prompting the user for a password. The format for the magic link is &lt;code&gt;https://&amp;lt;link-to-your-page&amp;gt;#&amp;lt;password&amp;gt;&lt;/code&gt;, placing the password in a &lt;a href="https://en.wikipedia.org/wiki/URI_fragment" rel="noopener noreferrer"&gt;URI fragment&lt;/a&gt;. Check out the &lt;a href="https://github.com/Greenheart/pagecrypt#share-a-magic-link-to-let-users-open-protected-pages-with-a-single-click" rel="noopener noreferrer"&gt;project's README section about magic links&lt;/a&gt; to better understand the security implications. When your browser goes to a URL containing a URI fragment, the fragment isn't sent across the internet, but it does remain in the browser's history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extend
&lt;/h2&gt;

&lt;p&gt;It would be interesting to extend PageCrypt to do a number of things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automate the inlining of CSS, JavaScript, and image files&lt;/li&gt;
&lt;li&gt;Encrypt multiple HTML files&lt;/li&gt;
&lt;li&gt;Encrypt multiple files, including CSS, JavaScript, and image files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you do end up extending it in your build process, &lt;a href="https://twitter.com/crc" rel="noopener noreferrer"&gt;let me know&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Explore
&lt;/h2&gt;

&lt;p&gt;This version of PageCrypt is a rewrite of an &lt;a href="https://github.com/MaxLaumeister/PageCrypt" rel="noopener noreferrer"&gt;older version of PageCrypt&lt;/a&gt;. That older version also inspired a few spin-offs that you might find useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/MaxLaumeister/pagecrypt/tree/master/python" rel="noopener noreferrer"&gt;Python CLI&lt;/a&gt; for PageCrypt&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/MaxLaumeister/pagecrypt/tree/master/PowerShell" rel="noopener noreferrer"&gt;PowerShell CLI&lt;/a&gt; for PageCrypt&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/brentscott93/pagecryptr" rel="noopener noreferrer"&gt;R-wrapper&lt;/a&gt; for PageCrypt&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/robinmoisson/staticrypt" rel="noopener noreferrer"&gt;StatiCrypt&lt;/a&gt; - A separate but similar project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now try it out yourself! You can deploy the &lt;a href="https://github.com/render-examples/pagecrypt" rel="noopener noreferrer"&gt;code&lt;/a&gt; to Render for free. The README provides step-by-step instructions.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>security</category>
    </item>
    <item>
      <title>What's New in PostgreSQL 14?</title>
      <dc:creator>Chris Castle</dc:creator>
      <pubDate>Tue, 05 Oct 2021 17:19:07 +0000</pubDate>
      <link>https://dev.to/render/what-s-new-in-postgresql-14-4669</link>
      <guid>https://dev.to/render/what-s-new-in-postgresql-14-4669</guid>
      <description>&lt;p&gt;PostgreSQL 14, released on September 30, 2021, introduces many new features and improvements. Two I find particularly interesting are &lt;a href="https://www.postgresql.org/docs/14/datatype-json.html#JSONB-SUBSCRIPTING" rel="noopener noreferrer"&gt;JSON syntax conveniences&lt;/a&gt; and the &lt;a href="https://www.postgresql.org/docs/14/rangetypes.html#RANGETYPES-BUILTIN" rel="noopener noreferrer"&gt;multirange data type&lt;/a&gt;. But there are also several noticeable performance improvements that many applications will benefit from without any code changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSON Conveniences
&lt;/h2&gt;

&lt;p&gt;PostgreSQL can store and manipulate JSON data. There are &lt;a href="https://www.postgresql.org/docs/current/datatype-json.html" rel="noopener noreferrer"&gt;JSON and JSONB data types&lt;/a&gt;. They are similar, but most of the time you probably want to use JSONB. The JSON data type is stored as a string whereas JSONB is stored in a decomposed binary format that allows PostgreSQL to query and manipulate nested strucutres inside the JSON more efficiently. JSONB also allows you to create indexes based on nested values. I'll be using JSONB exclusively here.&lt;/p&gt;

&lt;p&gt;Let's create a table and put some JSON data in it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;jsonb_example&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;bigserial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt;
  &lt;span class="n"&gt;jsonb_example&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{"a": {"b": ["foo", "hello"]}}'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{"a": {"b": ["chris", "render"]}}'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the table we just created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;jsonb_example&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;               &lt;span class="k"&gt;data&lt;/span&gt;
&lt;span class="c1"&gt;----+-----------------------------------&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;"chris"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"render"&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool. But what if we want to query nested values within the JSON? Here's how you used to have to do it with PostgreSQL 13.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="o"&gt;#&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'{a,b,1}'&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;jsonb_example&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="k"&gt;column&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
&lt;span class="c1"&gt;----------&lt;/span&gt;
 &lt;span class="nv"&gt;"hello"&lt;/span&gt;
 &lt;span class="nv"&gt;"render"&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;#&amp;gt;&lt;/code&gt; is an operator that tells PostgreSQL to return a JSON sub-object. PostgreSQL has a lot of &lt;a href="https://www.postgresql.org/docs/13/functions-json.html#FUNCTIONS-JSON-OP-TABLE" rel="noopener noreferrer"&gt;other operators&lt;/a&gt; for working with JSON like &lt;code&gt;#&amp;gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;-&amp;gt;&lt;/code&gt;, and &lt;code&gt;-&amp;gt;&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;{a,b,1}&lt;/code&gt; is a path into the JSON object that says&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Grab the value of property &lt;code&gt;a&lt;/code&gt;: &lt;code&gt;{"b": ["foo", "hello"]}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Within that, give me the value of property &lt;code&gt;b&lt;/code&gt;: &lt;code&gt;["foo", "hello"]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Within that array, give me the 2nd element: &lt;code&gt;"hello"&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With PostgreSQL 14 you can now access JSON sub-objects in a way that more closely matches how other programming languages reference JSON values. This new functionality is called &lt;a href="https://www.postgresql.org/docs/14/datatype-json.html#JSONB-SUBSCRIPTING" rel="noopener noreferrer"&gt;jsonb subscripting&lt;/a&gt;. Here's an example returning the same data as above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;jsonb_example&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="k"&gt;data&lt;/span&gt;
&lt;span class="c1"&gt;----------&lt;/span&gt;
 &lt;span class="nv"&gt;"hello"&lt;/span&gt;
 &lt;span class="nv"&gt;"render"&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use the same syntax to filter data with a &lt;code&gt;WHERE&lt;/code&gt; clause.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;jsonb_example&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'"render"'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;               &lt;span class="k"&gt;data&lt;/span&gt;
&lt;span class="c1"&gt;----+-----------------------------------&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;"chris"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"render"&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we can update values within the JSON object using the same syntax.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;jsonb_example&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'"bar"'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;jsonb_example&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;              &lt;span class="k"&gt;data&lt;/span&gt;
&lt;span class="c1"&gt;----+---------------------------------&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"render"&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This new syntax closely matches how you might access nested values in a JSON object with JavaScript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;foo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}};&lt;/span&gt;
&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// evaluates to 'hello'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Multirange Data Type
&lt;/h2&gt;

&lt;p&gt;PostgreSQL has had range data types &lt;a href="https://www.postgresql.org/about/news/postgresql-92-released-1415/" rel="noopener noreferrer"&gt;since version 9.2&lt;/a&gt;. Now there are multirange data types.&lt;/p&gt;

&lt;p&gt;A range data type is useful when you want a single value to represent a range of dates, time, or numbers. For example, you might use a range data type to represent meeting schedules or radio spectrum allocation. With a range data type, you only need a single column to store what would otherwise require two columns--start of range and end of range.&lt;/p&gt;

&lt;p&gt;Prior to PostgreSQL 14, that range could only be a single contiguous range. Now with multiranges, multiple discontiguous (but not overlapping) ranges can be stored as a single value.&lt;/p&gt;

&lt;p&gt;Let's look at a meeting scheduling example derived from the PostgreSQL &lt;a href="https://www.postgresql.org/docs/14/rangetypes.html#RANGETYPES-EXAMPLES" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;during&lt;/span&gt; &lt;span class="n"&gt;tsrange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tsrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2021-01-01 14:30'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2021-01-01 15:30'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="n"&gt;room&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;                    &lt;span class="n"&gt;during&lt;/span&gt;
&lt;span class="c1"&gt;------+-----------------------------------------------&lt;/span&gt;
 &lt;span class="mi"&gt;1108&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;"2021-01-01 14:30:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;"2021-01-01 15:30:00"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This row represents a room reservation for a meeting from 14:30 to 15:30 on January 1, 2021. But what if instead of a one hour meeting, we were planning an all-day meeting with a lunch break in the middle? What would a row for that reservation look like?&lt;/p&gt;

&lt;p&gt;Prior to PostgreSQL 14's multirange support, it would have to be stored as two rows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tsrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2021-01-01 09:00'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2021-01-01 12:00'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tsrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2021-01-01 13:00'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2021-01-01 17:00'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="n"&gt;room&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;                    &lt;span class="n"&gt;during&lt;/span&gt;
&lt;span class="c1"&gt;------+-----------------------------------------------&lt;/span&gt;
 &lt;span class="mi"&gt;1108&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;"2021-01-01 09:00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;"2021-01-01 12:00:00"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="mi"&gt;1108&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;"2021-01-01 13:00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;"2021-01-01 17:00:00"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But then we'd need to add a &lt;code&gt;meeting_id&lt;/code&gt; column so that we know these two rows represent the same meeting.&lt;/p&gt;

&lt;p&gt;With multiranges we only need one row to represent this meeting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;during&lt;/span&gt; &lt;span class="n"&gt;tsmultirange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tsmultirange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;tsrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2021-01-01 09:00'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2021-01-01 12:00'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;tsrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2021-01-01 13:00'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2021-01-01 17:00'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="n"&gt;room&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;                                            &lt;span class="n"&gt;during&lt;/span&gt;
&lt;span class="c1"&gt;------+-----------------------------------------------------------------------------------------------&lt;/span&gt;
 &lt;span class="mi"&gt;1108&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="nv"&gt;"2021-01-01 09:00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;"2021-01-01 12:00:00"&lt;/span&gt;&lt;span class="p"&gt;),[&lt;/span&gt;&lt;span class="nv"&gt;"2021-01-01 13:00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;"2021-01-01 17:00:00"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Improved Handling of Concurrent Connections
&lt;/h2&gt;

&lt;p&gt;A lot has happened behind the scenes to improve PostgreSQL performance. However, PostgreSQL 14's handling of multiple client connections will likely be the performance improvement noticeable to the most people. The folks at pganalyze did some great &lt;a href="https://pganalyze.com/blog/postgres-14-performance-monitoring" rel="noopener noreferrer"&gt;performance testing using &lt;code&gt;pgbench&lt;/code&gt;&lt;/a&gt; to verify this. At 5000 active connections, they saw about a 20% improvement in throughput over PostgreSQL 13. At 10,000 connections, that improvement went up to 50%. Of course, these results are dependent on your hardware, schema, data, and queries, so you may not see the same results, but the consensus seems to be that PostgreSQL's handling of concurrent connections has improved significantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  And Much More
&lt;/h2&gt;

&lt;p&gt;There are many more new features and improvements, which you can read about in the &lt;a href="https://www.postgresql.org/about/news/postgresql-14-released-2318/" rel="noopener noreferrer"&gt;PostgreSQL 14 release announcement&lt;/a&gt; or compare across versions in the &lt;a href="https://www.postgresql.org/about/featurematrix/" rel="noopener noreferrer"&gt;feature matrix&lt;/a&gt; (deselect all versions except 14 and the one you want to compare to and select "Hide unchanged features").&lt;/p&gt;

&lt;h2&gt;
  
  
  Render Supports PostgreSQL 14
&lt;/h2&gt;

&lt;p&gt;I'd be remiss not to mention that Render was the first cloud provider I know to support PostgreSQL 14, making it available on the same day PostgreSQL 14 was released and giving our customers access to all these exciting improvements in just a few clicks. &lt;a href="https://dashboard.render.com/new/database" rel="noopener noreferrer"&gt;Try it out&lt;/a&gt; and see how it works for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3o9fk9ybtn3it34tlryb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3o9fk9ybtn3it34tlryb.png" alt="A screenshot of the Render dashboard while creating a new PostgreSQL 14 database" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>tutorial</category>
      <category>sql</category>
    </item>
    <item>
      <title>Evolving Alongside your Tech Stack</title>
      <dc:creator>Chris Castle</dc:creator>
      <pubDate>Thu, 30 Apr 2020 15:40:59 +0000</pubDate>
      <link>https://dev.to/heroku/evolving-alongside-your-tech-stack-4b99</link>
      <guid>https://dev.to/heroku/evolving-alongside-your-tech-stack-4b99</guid>
      <description>&lt;p&gt;&lt;em&gt;This blog post is adapted from a discussion during &lt;a href="https://www.heroku.com/podcasts/codeish/39-evolving-alongside-your-tech-stack"&gt;an episode of our podcast, Code[ish]&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Over the last twenty years, software development has advanced so rapidly that it's possible to create amazing user experiences, powerful machine learning algorithms, and memory efficient applications with incredible ease. But as the capabilities tech provides has changed, so too have the requirements of individual developers morphed to encompass a variety of skills. Not only should you be writing efficient code; you need to understand how that code communicates with all the other systems involved and make it all work together.&lt;/p&gt;

&lt;p&gt;In this post, we'll explore how you can stay on top of the changing software development landscape, without sacrificing your desires to learn or the success of your product.&lt;/p&gt;

&lt;h2&gt;
  
  
  User experience depends on technical expertise
&lt;/h2&gt;

&lt;p&gt;When the iPhone first came out in 2007, it was rather limited in technical capabilities. There was no support for multitasking and gestures, no ability to copy and paste text, and there wasn't any support for third-party software. It's not that these ideas were not useful, it’s just that the first generation of the phone's hardware and operating system could not support such features. This serves as a good example to underscore how UX has sometimes been constrained by technology.&lt;/p&gt;

&lt;p&gt;Now, the situation has changed somewhat. Tools have advanced to the point where it's really easy to create a desktop or mobile app which accepts a variety of gestures and inputs. The consequences of this are twofold. First, users have come to expect a certain level of quality in software. Gone are the days of simply "throwing something together"; software, websites, and mobile apps all need to look polished. This requires developers to have a high level of design sensibility (or work with someone else who does). Second, it means that the role of the engineer has expanded beyond just writing code. They need to understand why they're building whatever it is they're building, why it's important to their users, and how it functionally integrates with the rest of the app. If you design an API, for example, you’ll need to secure it against abuse; if you design a custom search index, you need to make sure users can actually find what they’re looking for.&lt;/p&gt;

&lt;p&gt;On the one hand, because you're running on the same devices and platforms as your users (whether that a smartphone or an operating system), you're intricately familiar with the best UI patterns—how a button should operate, which transitions to make between screens—because every other app has made similar considerations. But on the other hand, you also need to deal with details such as memory management and CPU load to ensure the app is running optimally.&lt;/p&gt;

&lt;p&gt;It’s not enough for an app to work well, as it must also look good. It's important to find a balance of both design sensibilities and technical limitations—or at least, a baseline knowledge of how everything works—in order to ship quality software.&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow everything but only learn &lt;em&gt;some&lt;/em&gt; things
&lt;/h2&gt;

&lt;p&gt;When it comes to personal growth, learning to prioritize solutions to the problems you encounter can be critical in your development. For example, suppose you notice one day that your Postgres queries are executing slower than you would like. You should have a general awareness of how higher rates of traffic affects your database querying strategies, or how frequent writes affect the physical tables on disk. But that doesn't necessarily mean that you should sink a massive amount of time and effort to fine-tune these issues towards the most optimal strategy. When developing software, you will always have one of several choices to make, and rarely does one become the only true path forward. Sometimes, having the insight to know the trade-offs and accepting one sub-optimal approach above another makes it easier to cut losses and focus on the parts of your software which matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;Sometimes, having the insight to know the trade-offs and accepting one sub-optimal approach above another makes it easier to cut losses and focus on the parts of your software which matter.&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;It seems like every year, a new web framework or programming language is released. This makes it difficult, if not impossible, to follow every single new item when they are announced. The inverse is also true. We might feel that adopting new technologies is one way to stay "relevant," but this attitude can be quite dangerous. If you are an early adopter, you run the risk of being on the hook for finding bugs, distracting you from your actual goal of shipping features for your own application. You should take a calculated approach to the pros and cons of any new tech. For example, switching your database entirely to MemSQL because you heard it's "faster" is less reasonable than making a switch after reading someone's careful evaluation of the technology, and realizing that it matched your own needs as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping calm and steady
&lt;/h2&gt;

&lt;p&gt;At the end of the day, you should be very invested in your own stack and the ecosystem you work in. That work can be something as simple as reading Medium posts or following Twitter accounts. Broaden your knowledge of other services outside your own realm of expertise only if you come across someone confronting problems similar to yours. You should own tools which you know how to operate, rather than keep a shed full of all sorts of shiny objects.&lt;/p&gt;

</description>
      <category>career</category>
      <category>tips</category>
      <category>learning</category>
      <category>technology</category>
    </item>
    <item>
      <title>CLI Flags in Practice + How to Make Your Own CLI Command with oclif</title>
      <dc:creator>Chris Castle</dc:creator>
      <pubDate>Thu, 09 May 2019 16:09:15 +0000</pubDate>
      <link>https://dev.to/heroku/cli-flags-in-practice-how-to-make-your-own-cli-command-with-oclif-26in</link>
      <guid>https://dev.to/heroku/cli-flags-in-practice-how-to-make-your-own-cli-command-with-oclif-26in</guid>
      <description>&lt;p&gt;&lt;em&gt;Editor's note: If you like CLIs, you should check out &lt;a href="https://oclif.io/conf"&gt;oclifconf&lt;/a&gt; taking place on Friday, May 31st, 2019 in San Francisco. It’s the first community get-together for oclif! Space is limited so let us know soon if you are interested in joining.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What is it that makes working from the command line so empowering? It can feel archaic at times, sure, but when you remember the right sequence of words, characters, and symbols for what you’re trying to do, it hits you with a sense of accomplishment and mastery over your tools that no graphical interface can compete with.&lt;/p&gt;

&lt;p&gt;So what better way to continue your adventures as a developer than by developing your own CLI tool?&lt;/p&gt;

&lt;p&gt;In this post, we’ll go over what type of parameters CLI commands take—also known as "flags", "arguments", and sometimes "options.” Then, we’ll start you off with &lt;a href="https://oclif.io/"&gt;oclif&lt;/a&gt;, the CLI framework that makes it easy to create new CLI commands!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Syntax of a CLI Command
&lt;/h2&gt;

&lt;p&gt;Any command line interface command has a few standard "parts of speech.” As a user of CLI tools, knowing these parts of speech can help you make fewer typos. It can also help you understand complex commands other people share with you more quickly (like &lt;a href="https://www.commandlinefu.com/commands/browse"&gt;these&lt;/a&gt;). If you are designing a CLI tool it is even more important to understand these parts of speech, so you can come up with the most ergonomic interface for your users. Yes, a CLI is a user interface!&lt;/p&gt;

&lt;p&gt;Some of you may recognize diagrams like the one below from elementary or primary school. Fortunately, understanding how CLI commands are structured isn’t going to feel like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--71TkDWcN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://heroku-blog-files.s3.amazonaws.com/posts/1557265553-sentence-diagram.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--71TkDWcN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://heroku-blog-files.s3.amazonaws.com/posts/1557265553-sentence-diagram.png" alt="sentence-diagram" width="390" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CLI commands are pretty straightforward compared to the typical English sentence.&lt;/p&gt;

&lt;p&gt;For starters, let’s look at the parameters that appear to the right of CLI commands. Sure, there are many ways you can pass data to a CLI command, but these three types of parameters to the "right" of the command might be the most common: argument, long flag, and short flag. These two formats for flags are the standard for GNU-style flags. Not all CLIs follow this convention, but it has become the most popular style on Unix-like and POSIX compliant operating systems.&lt;/p&gt;

&lt;p&gt;What better way for us to start than with the &lt;code&gt;ls&lt;/code&gt; command? It’s one of the most common and simplest commands on Unix-like operating systems. It simply lists the contents of a directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Command
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This command, &lt;code&gt;ls&lt;/code&gt;, works on its own, as a standalone command. Without any parameters, this command will list the contents of the current directory.&lt;/p&gt;
&lt;h3&gt;
  
  
  Argument
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But you can do the same thing with an argument! Turns out that &lt;code&gt;ls .&lt;/code&gt; and &lt;code&gt;ls&lt;/code&gt; are the same thing, with &lt;code&gt;ls&lt;/code&gt; simply using an implied &lt;code&gt;.&lt;/code&gt; directory. For those that don’t remember or don’t know, &lt;code&gt;.&lt;/code&gt; always refers to the current directory.&lt;/p&gt;

&lt;p&gt;But now, the argument syntax makes it possible for you to pass any directory path to &lt;code&gt;ls&lt;/code&gt;, and to get a look at what’s in there.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls /home/casey/code/some-repo-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;An argument is anything to the right of a command that is not a flag (we’ll get to flags next). And fortunately, an argument can come before or after flags–it can coexist with them happily.&lt;/p&gt;
&lt;h3&gt;
  
  
  Long Flag
&lt;/h3&gt;

&lt;p&gt;To list files that are normally hidden (like &lt;code&gt;~/.bashrc&lt;/code&gt;), you can use a flag on the &lt;code&gt;ls&lt;/code&gt; command. &lt;code&gt;ls --all&lt;/code&gt; is the long flag form. A long flag always uses a double dash, and it is always represented by multiple characters.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls --all
$ ls . --all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Short Flag
&lt;/h3&gt;

&lt;p&gt;There is also a short flag form of this flag: &lt;code&gt;ls -a&lt;/code&gt;. The &lt;code&gt;a&lt;/code&gt; is short for &lt;code&gt;all&lt;/code&gt; in this case. A short flag always uses a single dash, and it is always represented by a single letter.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls -a
$ ls . -a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Short flags can stack too, so you don't need a separate dash for each one. Order does not matter for these, unless passing a &lt;em&gt;flag argument&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls -la
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Flag Argument
&lt;/h3&gt;

&lt;p&gt;Many flags accept an option called a "flag argument" (not to be confused with a "command argument"). In general a command's parameters can be in any order, but flags that accept options must have the option directly after the flag. That way, the command doesn’t get confused by non-flag arguments.&lt;/p&gt;

&lt;p&gt;For an example, here the &lt;code&gt;-x&lt;/code&gt; flag does not accept an option but the &lt;code&gt;-f&lt;/code&gt; flag does. &lt;code&gt;archive.tar&lt;/code&gt; is the option being passed to &lt;code&gt;-f&lt;/code&gt;. Both of these are valid.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ tar -x -f archive.tar
$ tar -xf archive.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A flag and its option can be separated by a space or an equals sign &lt;code&gt;=&lt;/code&gt;. Interestingly, short flags (but not long flags) can even skip the space, although many people find it much easier to read with the space or equals sign.These three are all valid and equivalent.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ tar -f archive.tar
$ tar -f=archive.tar
$ tar -farchive.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Long flags must have a space or equals sign to separate the flag from its option.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git log --pretty=oneline
$ git log --pretty oneline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Other Ways of Passing Data
&lt;/h3&gt;

&lt;p&gt;We've covered parameters, which are arguments, long flags, and short flags. There are two other ways to pass data to a command: &lt;a href="https://en.wikipedia.org/wiki/Environment_variable"&gt;environment variables ("env vars")&lt;/a&gt;, or &lt;a href="https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)"&gt;standard input ("stdin")&lt;/a&gt;. These won't be covered in this blog post, but check out the links to learn more about them.&lt;/p&gt;
&lt;h2&gt;
  
  
  Building a New Command With &lt;code&gt;oclif&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Scenario: we want to design an oclif command that takes an input like "Casey", and returns "hi, Casey!". There are many ways the user could pass this in. Here we show an example of each type of input using an argument, a long flag, and a short flag.&lt;/p&gt;

&lt;p&gt;First, let’s get started with &lt;code&gt;oclif&lt;/code&gt;. Getting a CLI app going is very, very straightforward with it. Open up your terminal and type the following, which will use &lt;code&gt;npx&lt;/code&gt; to run &lt;code&gt;oclif&lt;/code&gt; and then create a new CLI. &lt;code&gt;npx&lt;/code&gt; is a pretty useful command to &lt;a href="https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b"&gt;simplify running CLI tools and other executables hosted on the npm registry&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npx oclif single greet-me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We won’t go into the details of the &lt;code&gt;single&lt;/code&gt; (vs &lt;code&gt;multi&lt;/code&gt;) argument above. Check out the &lt;a href="https://oclif.io/docs/introduction.html#single-command-or-multi-command"&gt;oclif documentation&lt;/a&gt; for more about this.&lt;/p&gt;

&lt;p&gt;Now, you’ll get the chance to specify some details of your new CLI, including the command name. When it asks you, just press enter, choosing the deafult. It’ll take the &lt;code&gt;greet-me&lt;/code&gt; argument you passed into the above command. You can choose the default for most of the questions it asks you. The answers won't make much of a difference for this simple tutorial. However, they are very important to answer accurately if you will be sharing your CLI command with others.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;? npm package name: greet-me
? command bin name the CLI will export: greet-me

...

Created greet-me in /home/casey/code/greet-me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now that we have things set up, let’s check out what’s happening in &lt;code&gt;/greet-me/src/index.ts&lt;/code&gt;, where all the important argument and flag-handling code for your CLI will live.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@oclif/command&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GreetMeCommand&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GreetMeCommand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;world&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`hello &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; from ./src/index.js`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;GreetMeCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Describe the command here
...
Extra documentation goes here
`&lt;/span&gt;

&lt;span class="nx"&gt;GreetMeCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// add --version flag to show CLI version&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;char&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="c1"&gt;// add --help flag to show CLI version&lt;/span&gt;
  &lt;span class="na"&gt;help&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;help&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;char&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;char&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name to print&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="c1"&gt;// flag with no value (-f, --force)&lt;/span&gt;
  &lt;span class="na"&gt;force&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;char&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;f&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GreetMeCommand&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What we can see here is that it accepts a few different flag names out the gate (&lt;code&gt;version&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;help&lt;/code&gt;, and &lt;code&gt;force&lt;/code&gt;) by registering them in the &lt;code&gt;flags&lt;/code&gt; object.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* … */&lt;/span&gt;
    &lt;span class="nl"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;char&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="cm"&gt;/* … */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here, with the &lt;code&gt;version&lt;/code&gt; flag, the key acts as the ‘version’ long flag name, and on the right side of the expression, we use the method's in &lt;code&gt;oclif&lt;/code&gt;’s &lt;code&gt;flags&lt;/code&gt; module to register a flag, a type it’ll return, and the short flag name.&lt;/p&gt;

&lt;p&gt;Now, we’re ready to rock: let’s see just how many things &lt;code&gt;oclif&lt;/code&gt; handles out of the box by running the CLI. Right now, it’s only available with a slightly awkward command.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./bin/run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But &lt;a href="https://docs.npmjs.com/cli/link.html"&gt;npm allows&lt;/a&gt; us to symlink this to the name of our CLI.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm link

...

$ greet-me
&amp;gt; hello world from ./src/index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Excellent! Try passing your name in using &lt;code&gt;-n&lt;/code&gt; or &lt;code&gt;--name&lt;/code&gt; next–and see if there are any other ways &lt;code&gt;oclif&lt;/code&gt; will let you pass in arguments.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;SIGTERM&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;While that’s all we’re going to cover in this blog post, oclif has a growing community and its code is open source so there are lots of other ways to learn more. Here are some links to continue exploring oclif.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An episode of the &lt;a href="https://www.heroku.com/podcasts/codeish"&gt;Code[ish] podcast&lt;/a&gt; about oclif with Jeff Dickey, one of the creators of oclif, and Nahid Samsami, Heroku’s PM for oclif &lt;div class="podcastliquidtag"&gt;
  &lt;div class="podcastliquidtag__info"&gt;
    &lt;a href="/codeish/13-oclif-an-open-source-cli-framework"&gt;
      &lt;h1 class="podcastliquidtag__info__episodetitle"&gt;13. oclif: An Open Source CLI Framework&lt;/h1&gt;
    &lt;/a&gt;
    &lt;a href="/codeish"&gt;
      &lt;h2 class="podcastliquidtag__info__podcasttitle"&gt;
        Code[ish]
      &lt;/h2&gt;
    &lt;/a&gt;
  &lt;/div&gt;
  &lt;div id="record-13-oclif-an-open-source-cli-framework" class="podcastliquidtag__record"&gt;
    &lt;img class="button play-butt" id="play-butt-13-oclif-an-open-source-cli-framework" src="https://res.cloudinary.com/practicaldev/image/fetch/s--CeTiX2T4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/playbutt-5e444a2eae28832efea0dec3342ccf28a228b326c47f46700d771801f75d6b88.png" alt="play"&gt;
    &lt;img class="button pause-butt" id="pause-butt-13-oclif-an-open-source-cli-framework" src="https://res.cloudinary.com/practicaldev/image/fetch/s--dhrmSY9R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/pausebutt-bba7cb5f432cfb16510e78835378fa22f45fa6ae52a624f7c9794fefa765c384.png" alt="pause"&gt;
    &lt;img class="podcastliquidtag__podcastimage" id="podcastimage-13-oclif-an-open-source-cli-framework" alt="Code[ish]" src="https://res.cloudinary.com/practicaldev/image/fetch/s--aZx60jRN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.dev.to/cdn-cgi/image/width%3D%2Cheight%3D%2Cfit%3Dcover%2Cgravity%3Dauto%2Cformat%3Dauto/https%253A%252F%252Fdev-to-uploads.s3.amazonaws.com%252Fuploads%252Fpodcast%252Fimage%252F65%252F75dd0ad2-02ae-44e2-831d-4bb173f40fff.png"&gt;
  &lt;/div&gt;

  &lt;div class="hidden-audio" id="hidden-audio-13-oclif-an-open-source-cli-framework"&gt;
  
    
    Your browser does not support the audio element.
  
  &lt;div id="progressBar" class="audio-player-display"&gt;
    &lt;a href="/codeish/13-oclif-an-open-source-cli-framework"&gt;
      &lt;img id="episode-profile-image" alt="13. oclif: An Open Source CLI Framework" width="420" height="420" src="https://res.cloudinary.com/practicaldev/image/fetch/s--MYpwmOHw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.dev.to/cdn-cgi/image/width%3D420%2Cheight%3D420%2Cfit%3Dcover%2Cgravity%3Dauto%2Cformat%3Dauto/https%253A%252F%252Fdev-to-uploads.s3.amazonaws.com%252Fuploads%252Fpodcast%252Fimage%252F65%252F75dd0ad2-02ae-44e2-831d-4bb173f40fff.png"&gt;
      &lt;img id="animated-bars" src="https://res.cloudinary.com/practicaldev/image/fetch/s--umIkECOe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev.to/assets/animated-bars-4e8c57c8b58285fcf7d123680ad8af034cd5cd43b4d9209fe3aab49d1e9d77b3.gif" alt="animated volume bars"&gt;
    &lt;/a&gt;
    &lt;span id="barPlayPause"&gt;
      &lt;img class="butt play-butt" alt="play" src="https://res.cloudinary.com/practicaldev/image/fetch/s--CeTiX2T4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/playbutt-5e444a2eae28832efea0dec3342ccf28a228b326c47f46700d771801f75d6b88.png"&gt;
      &lt;img class="butt pause-butt" alt="pause" src="https://res.cloudinary.com/practicaldev/image/fetch/s--dhrmSY9R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/pausebutt-bba7cb5f432cfb16510e78835378fa22f45fa6ae52a624f7c9794fefa765c384.png"&gt;
    &lt;/span&gt;
    &lt;span id="volume"&gt;
      &lt;span id="volumeindicator" class="volume-icon-wrapper showing"&gt;
        &lt;span id="volbutt"&gt;
          &lt;img alt="volume" class="icon-img" height="16" width="16" src="https://res.cloudinary.com/practicaldev/image/fetch/s--SwdlmIWr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/volume-cd20707230ae3fc117b02de53c72af742cf7d666007e16e12f7ac11ebd8130a7.png"&gt;
        &lt;/span&gt;
        &lt;span class="range-wrapper"&gt;
          
        &lt;/span&gt;
      &lt;/span&gt;
      &lt;span id="mutebutt" class="volume-icon-wrapper hidden"&gt;
        &lt;img alt="volume-mute" class="icon-img" height="16" width="16" src="https://res.cloudinary.com/practicaldev/image/fetch/s--4zUzdXgY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/volume-mute-8f08ec668105565af8f8394eb18ab63acb386adbe0703afe3748eca8f2ecbf3b.png"&gt;
      &lt;/span&gt;
      &lt;span class="speed" id="speed"&gt;1x&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class="buffer-wrapper" id="bufferwrapper"&gt;
      &lt;span id="buffer"&gt;&lt;/span&gt;
      &lt;span id="progress"&gt;&lt;/span&gt;
      &lt;span id="time"&gt;initializing...&lt;/span&gt;
      &lt;span id="closebutt"&gt;×&lt;/span&gt;
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;💻🎙️ &lt;a href="https://oclif.io/conf"&gt;oclifconf details&lt;/a&gt; if you’ll be in the San Francisco Bay Area on Friday, May 31, 2019&lt;/li&gt;
&lt;li&gt;👀🗣️ oclif’s &lt;a href="https://spectrum.chat/oclif"&gt;Spectrum community&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;And finally, oclif’s GitHub repository &lt;div class="ltag-github-readme-tag"&gt;
&lt;br&gt;
  &lt;div class="readme-overview"&gt;
&lt;br&gt;
    &lt;h2&gt;
&lt;br&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;&lt;br&gt;
      &lt;a href="https://github.com/oclif"&gt;&lt;br&gt;
        oclif&lt;br&gt;
      &lt;/a&gt; / &lt;a href="https://github.com/oclif/oclif"&gt;&lt;br&gt;
        oclif&lt;br&gt;
      &lt;/a&gt;&lt;br&gt;
    &lt;/h2&gt;
&lt;br&gt;
    &lt;h3&gt;
&lt;br&gt;
      CLI for generating, building, and releasing oclif CLIs. Built by Salesforce.&lt;br&gt;
    &lt;/h3&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="ltag-github-body"&gt;
&lt;br&gt;
    

&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/449385/38243295-e0a47d58-372e-11e8-9bc0-8c02a6f4d2ac.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y_ifzHDm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://user-images.githubusercontent.com/449385/38243295-e0a47d58-372e-11e8-9bc0-8c02a6f4d2ac.png" width="260" height="73"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;
&lt;code&gt;oclif&lt;/code&gt; CLI&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://npmjs.org/package/oclif" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/5d713c0005333988706bc2181ac22e7dae24e99f021e73a4f623436313506e78/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f6f636c69662e737667" alt="Version"&gt;&lt;/a&gt;
&lt;a href="https://npmjs.org/package/oclif/oclif" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/fea448d2e2fb246d97432ad6da9fe43bfa79d529c15c3042b0be695794315cd0/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f64772f6f636c69662e737667" alt="Downloads/week"&gt;&lt;/a&gt;
&lt;a href="https://github.com/oclif/oclif/blob/main/package.json"&gt;&lt;img src="https://camo.githubusercontent.com/de6b00f823575bbdfab2ee17a38413f692979eec6e5692f1b39e6593d4f5127a/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f6c2f6f636c69662e737667" alt="License"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/oclif/oclif#oclif-cli"&gt;&lt;code&gt;oclif&lt;/code&gt; CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oclif/oclif#-description"&gt;🗒 Description&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oclif/oclif#-getting-started-tutorial"&gt;🚀 Getting Started Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oclif/oclif#-requirements"&gt;📌 Requirements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oclif/oclif#-migrating-from-v1"&gt;📌 Migrating from V1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oclif/oclif#-usage"&gt;🏗 Usage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oclif/oclif#-examples"&gt;📚 Examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oclif/oclif#-commands"&gt;🔨 Commands&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oclif/oclif#command-topics"&gt;Command Topics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oclif/oclif#-contributing"&gt;🚀 Contributing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oclif/oclif#-related-repositories"&gt;🏭 Related Repositories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oclif/oclif#-learn-more"&gt;🦔 Learn More&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🗒 Description&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;This is the &lt;code&gt;oclif&lt;/code&gt; CLI for the &lt;a href="https://github.com/oclif/core"&gt;Open CLI Framework&lt;/a&gt;, that supports the development of oclif plugins and CLIs.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://oclif.io" rel="nofollow"&gt;See the docs for more information&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🚀 Getting Started Tutorial&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;a href="http://oclif.io/docs/introduction" rel="nofollow"&gt;Getting Started tutorial&lt;/a&gt; is a step-by-step guide to introduce you to oclif. If you have not developed anything in a command line before, this tutorial is a great place to get started.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://github.com/oclif/oclif#-usage"&gt;Usage&lt;/a&gt; below for an overview of the &lt;code&gt;oclif&lt;/code&gt; CLI.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;📌 Requirements&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;Currently, Node 18+ is supported. We support the &lt;a href="https://nodejs.org/en/about/releases" rel="nofollow"&gt;LTS versions&lt;/a&gt; of Node. You can add the &lt;a href="https://www.npmjs.com/package/node" rel="nofollow"&gt;node&lt;/a&gt; package to your CLI to ensure users are running a specific version of Node.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;📌 Migrating from V1&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;If you have been using…&lt;/p&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/oclif/oclif"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>productivity</category>
      <category>node</category>
      <category>bash</category>
    </item>
    <item>
      <title>Beyond Web and Worker: Evolution of the Modern Web App on Heroku</title>
      <dc:creator>Chris Castle</dc:creator>
      <pubDate>Tue, 14 Aug 2018 18:59:00 +0000</pubDate>
      <link>https://dev.to/heroku/beyond-web-and-worker-evolution-of-the-modern-web-app-on-heroku-5c27</link>
      <guid>https://dev.to/heroku/beyond-web-and-worker-evolution-of-the-modern-web-app-on-heroku-5c27</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the first in a series of blog posts examining the evolution of web app architecture over the past 10 years. This post examines the forces that have driven the architectural changes and a high-level view of a new architecture. In future posts, we’ll zoom in to details of specific parts of the system.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The standard web application architecture suitable for many organizations has changed drastically in the past 10 years. Back in Heroku’s early days in 2008, a standard web application architecture consisted of a web process type to respond to HTTP requests, a database to persist data, and a worker process type plus Redis to manage a &lt;a href="https://devcenter.heroku.com/articles/background-jobs-queueing" rel="noopener noreferrer"&gt;job queue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F47i7fnvr8mazxdlbptv6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F47i7fnvr8mazxdlbptv6.png" alt="An image of a common web app architecture on Heroku in 2008" width="754" height="1168"&gt;&lt;/a&gt;Modern web app on Heroku in 2008&lt;/p&gt;

&lt;p&gt;Now, in 2018, a website (or mobile app) is the primary way many companies interact with customers. Technology is inextricably woven into the fabric of almost every business. As technology is being tasked to do more for a business, we as developers need new breeds of architectures to start from for the modern web app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fejkm70kxtbz4s46bbkra.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fejkm70kxtbz4s46bbkra.png" alt="An image of the evolution of the modern web app arhcitecture on Herkou" width="800" height="405"&gt;&lt;/a&gt;Sneak peek: Evolution of the modern web app architecture&lt;/p&gt;

&lt;h2&gt;
  
  
  The Change Drivers
&lt;/h2&gt;

&lt;p&gt;How have users’ expectations of computers, phones, and software changed in the past decade? What are the drivers of this change? And what do those changes imply for a new web app architecture?&lt;/p&gt;

&lt;h3&gt;
  
  
  Snappier Web Experiences
&lt;/h3&gt;

&lt;p&gt;The constraints of the web’s request / response pattern led to poor user experiences. Imagine if every installed application on your computer had to re-render the entire screen whenever you clicked something! And now—to simulate the web’s latency—add the restriction of using a computer from twenty years ago but running today’s software. This is what much of the web user experience is like. It’s shocking that we put up with it as much as we do.&lt;/p&gt;

&lt;p&gt;Our desire for richer, more interactive, and lower latency web experiences has driven the popularity of single page apps and the JavaScript tools required to build them. Use of convenience libraries like jQuery gave way to libraries like Backbone.js and Knockout.js that finally supported the creation of a cohesive, maintainable app that runs in the user’s browser.&lt;/p&gt;

&lt;p&gt;The next generation of libraries—where we are now in 2018—including React, Angular, Ember, and Vue, were born out of our experiences with the first generation plus the exploding popularity of Node.js (here, as a build tool) and rapid improvements to JavaScript (or maybe more accurately, ECMAScript with versions ES2015, 16, 17, etc).&lt;/p&gt;

&lt;p&gt;So what architectural changes do we need to make to allow our web application to support single page apps? First, we need a way to more efficiently deliver JavaScript to the user’s browser. Single page apps include much more JavaScript (and possibly more images, videos, CSS, and HTML) than we had previously sent to the browser. Second, it means we need a publicly exposed API from which the single page app can get its dynamic data.&lt;/p&gt;

&lt;p&gt;So here’s the list of new concepts we’re going to add to our architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Content Delivery Network&lt;/strong&gt; -- effectively a global network cache that can deliver JavaScript, HTML, CSS, and images to the user’s browser faster than our web server can.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway&lt;/strong&gt; -- a publicly exposed API from which the single page app can securely request the data it needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Smartphones
&lt;/h3&gt;

&lt;p&gt;Today most of us expect to find a mobile app for any product or service we want to interact with—or at least a web site that works well on our mobile devices. We now use mobile devices for so many things we could do only in the desktop browser just ten years ago—or even new things we could never do in the desktop browser, like receive a notification based on location, scan a digital coupon, or track a workout. And this trend doesn’t look like it’s going away: &lt;a href="https://www.statista.com/statistics/330695/number-of-smartphone-users-worldwide/" rel="noopener noreferrer"&gt;in 2011, 10% of the world’s population were smartphone users, and now in 2018, a third are smartphone users&lt;/a&gt;. In many countries like the United States, Sweden, and South Korea, &lt;a href="https://en.wikipedia.org/wiki/List_of_countries_by_smartphone_penetration" rel="noopener noreferrer"&gt;70% of the population is using smartphones&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The iOS App Store and Android Market (now Google Play) appeared in 2008 and ushered in the era of ubiquitous native mobile apps as mobile web browsers hadn’t and still don’t (although they are getting better and better) allow for the same user experience that a native app does.&lt;/p&gt;

&lt;p&gt;So what do we need to support a native mobile app in our architecture? We need a way for the app to communicate with our servers—to receive a push notification, receive a digital coupon, or save the GPS data for a user’s workout. Fortunately, we can use one of the concepts we added to support single page apps: an API Gateway.&lt;/p&gt;

&lt;h3&gt;
  
  
  API as a new UI
&lt;/h3&gt;

&lt;p&gt;It seems that every company wants its own API now. Obviously companies like Heroku, GitHub, and Amazon Web Services whose customers are developers, need an API, but now many companies whose customers aren’t developers—like &lt;a href="https://developer.bankofamerica.com/CPODevPortal/apidocs/public/APIDevPortal.html#/home" rel="noopener noreferrer"&gt;Bank of America&lt;/a&gt;, &lt;a href="http://developer.macys.com/io-docs" rel="noopener noreferrer"&gt;Macy’s&lt;/a&gt;, and &lt;a href="https://lillypad.lilly.com/entry.php?e=7165" rel="noopener noreferrer"&gt;Eli Lilly&lt;/a&gt;—have APIs. ProgrammableWeb’s &lt;a href="https://www.programmableweb.com/apis/directory?order=created&amp;amp;sort=desc" rel="noopener noreferrer"&gt;API Directory&lt;/a&gt; lists over 19,000 public APIs that companies or other organizations have built, most of them from companies whose primary customers are not developers. All these APIs are a kind of new user interface for developers.&lt;/p&gt;

&lt;p&gt;An API opens your product up to endless possibilities of creative extension by millions of software developers. Google Maps, Twilio, and Braintree probably didn’t think about ride-sharing as a use case for their APIs, but Uber and Lyft wouldn’t have been possible without a mapping, SMS, and payment API.&lt;/p&gt;

&lt;p&gt;An API also provides a standard point of integration with your product. Often the electrical socket analogy is used to explain this. Without a standardized electrical socket, we would have to manually wire each new device we get into our home’s electrical system. The electrical socket gives us a standard interface between a device and the electrical grid. Similarly, because there are common standards for APIs, like &lt;a href="https://en.wikipedia.org/wiki/Representational_state_transfer" rel="noopener noreferrer"&gt;REST&lt;/a&gt; over &lt;a href="https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol" rel="noopener noreferrer"&gt;HTTP&lt;/a&gt; using &lt;a href="https://en.wikipedia.org/wiki/JSON" rel="noopener noreferrer"&gt;JSON&lt;/a&gt; content type, we as developers need less time and proprietary expertise to integrate two disparate systems.&lt;/p&gt;

&lt;p&gt;As you might have guessed, this API can use the same architectural concept we added in the last section: an API Gateway.&lt;/p&gt;

&lt;h3&gt;
  
  
  Streaming Data (and lots of it)
&lt;/h3&gt;

&lt;p&gt;The software we create is producing data at an &lt;a href="https://en.wikipedia.org/wiki/Big_data" rel="noopener noreferrer"&gt;exponentially growing rate&lt;/a&gt;. And we are demanding much more from that data. We want data not only to build the user’s web or mobile experience, but we want it for reporting, for informing A/B tests or blue/green deploys, for user experience studies, for billing, and for compliance. And often we need a real-time view of that data with the flexibility to create different views to support evolving business requirements.&lt;/p&gt;

&lt;p&gt;The “data firehose” has emerged as a metaphor to describe harnessing all of this moving data. We need a data firehose to help move this data around in an organized way. It could be to move data from the point of production to storage, to allow internal services to communicate with each other, or to send real-time data to analysis and visualization tools.&lt;/p&gt;

&lt;p&gt;So what do we need to add to our architecture to manage, observe, analyze, and visualize streams of data?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://devcenter.heroku.com/articles/kafka-on-heroku" rel="noopener noreferrer"&gt;Apache Kafka&lt;/a&gt;&lt;/strong&gt; -- a tool that can manage large streams of data reliably and to which data producers and data consumers can easily attach themselves&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;AWS S3&lt;/a&gt; / &lt;a href="https://cloud.google.com/storage/" rel="noopener noreferrer"&gt;Google Cloud Storage&lt;/a&gt;&lt;/strong&gt; -- effectively unlimited and redundant storage for all the data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://aws.amazon.com/redshift/" rel="noopener noreferrer"&gt;AWS RedShift&lt;/a&gt; / &lt;a href="https://cloud.google.com/bigquery/" rel="noopener noreferrer"&gt;Google BigQuery&lt;/a&gt;&lt;/strong&gt; -- a data warehouse tool to organize the data and make it queryable quickly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://devcenter.heroku.com/articles/dataclips" rel="noopener noreferrer"&gt;Heroku Dataclips&lt;/a&gt; / &lt;a href="https://www.metabase.com/" rel="noopener noreferrer"&gt;Metabase&lt;/a&gt; / &lt;a href="https://looker.com/" rel="noopener noreferrer"&gt;Looker&lt;/a&gt;&lt;/strong&gt; -- data query and visualization tools that are simple to connect to the data warehouse and easy to start using&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Adaptability
&lt;/h3&gt;

&lt;p&gt;Ever-increasing complexity (i.e. business complexity, requirement complexity, software becoming more mission-critical to businesses, increased real-time expectations of users, growing data volumes, etc.) means developers need to build and operate more adaptable systems.&lt;/p&gt;

&lt;p&gt;An increasingly popular way to build a more adaptable system is to compose it from many small, discrete services. Call this technique what you want—microservices, distributed systems, service-oriented architecture, or even functions-as-a-service—the important concept is the composition of separately-maintained and deployed software components to form a single system.&lt;/p&gt;

&lt;p&gt;Building new functionality as separate services instead of within a monolith allows for easier separation of concern. As a developer, I can keep fewer concepts in my head while building or maintaining smaller services. As an engineering manager, my team can work on more in parallel. To be clear, this technique doesn’t come without pitfalls. Check out Ryan Townsend's &lt;a href="https://blog.heroku.com/monolithic-applications-into-services" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; on his company’s journey of deconstructing a monolith into services.&lt;/p&gt;

&lt;h3&gt;
  
  
  And Don’t Forget Scale
&lt;/h3&gt;

&lt;p&gt;Clearly, the need for scale is not new in 2018, but the addition of all these new architectural components makes scaling more difficult than it was a decade ago. Previously we could manage most of our scaling needs with three levers: horizontal scaling of web &lt;a href="https://devcenter.heroku.com/articles/dynos" rel="noopener noreferrer"&gt;dynos&lt;/a&gt; (Heroku’s lightweight container technology), horizontal scaling of worker dynos, and vertical scaling of PostgreSQL. Now we have dozens of levers to worry about. And further compounding the scale problem is the relentless growth of internet users. &lt;a href="https://en.wikipedia.org/wiki/Global_Internet_usage#Internet_users" rel="noopener noreferrer"&gt;Half the people on our beautiful blue marble have access to the internet!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is one area where PaaS and managed services show their value. The good ones reduce the number of levers you have to worry about to scale your system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bring It All Together
&lt;/h2&gt;

&lt;p&gt;So let’s look at all the components we need to add to our architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Content Delivery Network&lt;/strong&gt; -- effectively a global network cache that can deliver JavaScript, HTML, CSS, and images to the user’s browser faster than our web server can.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway&lt;/strong&gt; -- a publicly exposed API from which the single page app can securely request the data it needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://devcenter.heroku.com/articles/kafka-on-heroku" rel="noopener noreferrer"&gt;Apache Kafka&lt;/a&gt;&lt;/strong&gt; -- a tool that can manage large streams of data reliably and to which data producers and data consumers can easily attach themselves&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;AWS S3&lt;/a&gt; / &lt;a href="https://cloud.google.com/storage/" rel="noopener noreferrer"&gt;Google Cloud Storage&lt;/a&gt;&lt;/strong&gt; -- effectively unlimited and redundant storage for all the data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://aws.amazon.com/redshift/" rel="noopener noreferrer"&gt;AWS RedShift&lt;/a&gt; / &lt;a href="https://cloud.google.com/bigquery/" rel="noopener noreferrer"&gt;Google BigQuery&lt;/a&gt;&lt;/strong&gt; -- a data warehouse tool to organize the data and make it queryable quickly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://devcenter.heroku.com/articles/dataclips" rel="noopener noreferrer"&gt;Heroku Dataclips&lt;/a&gt; / &lt;a href="https://www.metabase.com/" rel="noopener noreferrer"&gt;Metabase&lt;/a&gt; / &lt;a href="https://looker.com/" rel="noopener noreferrer"&gt;Looker&lt;/a&gt;&lt;/strong&gt; -- data query and visualization tools that are simple to connect to the data warehouse and easy to start using&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the two new concepts we need to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service-oriented architecture for adaptability&lt;/li&gt;
&lt;li&gt;Scalable-first design so that scaling is a normal operations activity, not a herculean one at 2am&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s look at our new architecture from a high-level. All of the new components we discussed have been incorporated into a single system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F146af83cwucgimnq6bbb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F146af83cwucgimnq6bbb.png" alt="An image of a new modern web app architecture" width="" height=""&gt;&lt;/a&gt;A modern web app architecture in 2018&lt;/p&gt;

&lt;p&gt;Compared to the version from 2008, it looks like a lot to build and manage, right? It could be, if you try to build and manage it all yourself. The truth is that using a PaaS like Heroku makes deploying and managing something like this much easier than building it yourself on an IaaS (or your own data center &lt;em&gt;[shudder]&lt;/em&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking to the Future
&lt;/h2&gt;

&lt;p&gt;For sure, we still have many open questions. How do the dynos discover each other and communicate between themselves? What gets served from the CDN and how does it get there? How does Kafka write data to S3 or RedShift? How do code deploys work? How does CI/CD work? How do we monitor the health of all the components? What security concerns do we have to think about?&lt;/p&gt;

&lt;p&gt;In follow-up blog posts, we’ll zoom in to specific parts of this system and show you how to deploy them to Heroku. Until then, I hope this high-level architecture serves as a guide to the primary concepts many organizations need to think about as they’re building a web application in 2018.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Many thanks to those who helped me write this post: Vik Rana, Charlie Gleason, Jennifer Hooper, Scott Truitt, Jon Mountjoy, Jon Byrum, Michael Friis, Hunter Loftis, Stephanie Chung, and Jonathan Fulton’s &lt;a href="https://engineering.videoblocks.com/web-architecture-101-a3224e126947" rel="noopener noreferrer"&gt;Web Architecture 101&lt;/a&gt; post. Your reviews, discussions, ideas, inspiration, grammar help, drawings, and typo catches are greatly appreciated.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>microservices</category>
      <category>scale</category>
    </item>
  </channel>
</rss>
