<?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: Adam Coster</title>
    <description>The latest articles on DEV Community by Adam Coster (@adamcoster).</description>
    <link>https://dev.to/adamcoster</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%2F247112%2F15b3ffee-601f-4e00-95ff-c31ce8abe39f.jpg</url>
      <title>DEV Community: Adam Coster</title>
      <link>https://dev.to/adamcoster</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adamcoster"/>
    <language>en</language>
    <item>
      <title>Custom JavaScript/Typescript Errors: Why and how to use them</title>
      <dc:creator>Adam Coster</dc:creator>
      <pubDate>Wed, 20 Dec 2023 19:27:47 +0000</pubDate>
      <link>https://dev.to/adamcoster/custom-javascripttypescript-errors-why-and-how-to-use-them-1a5f</link>
      <guid>https://dev.to/adamcoster/custom-javascripttypescript-errors-why-and-how-to-use-them-1a5f</guid>
      <description>&lt;p&gt;Custom error classes are great:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can add your own fields, like error codes and such&lt;/li&gt;
&lt;li&gt;You can simplify your error-handling logic while also making it more robust&lt;/li&gt;
&lt;li&gt;You can add logging and other useful functionality right into the error class&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post includes a short discussion about how and why to create custom JavaScript errors, plus JavaScript (and Typescript) templates and Visual Studio Code snippets to make it easy to create your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to write a custom JavaScript Error class
&lt;/h2&gt;

&lt;p&gt;Creating a custom error class in JavaScript is pretty straight-forward: you just need to extend the base &lt;code&gt;Error&lt;/code&gt; class:&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;class&lt;/span&gt; &lt;span class="nc"&gt;MyError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's really all it takes! Then you can create and throw &lt;code&gt;MyError&lt;/code&gt; instances just as you would regular &lt;code&gt;Error&lt;/code&gt;s:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MyError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Oh no!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The one additional thing I &lt;em&gt;always&lt;/em&gt; add to my custom error classes is a call to the static &lt;code&gt;Error.captureStackTrace&lt;/code&gt;, which is &lt;a href="https://v8.dev/docs/stack-trace-api#stack-trace-collection-for-custom-exceptions"&gt;available on V8-based runtimes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Passing your error's constructor to &lt;code&gt;Error.captureStackTrace&lt;/code&gt; makes it so that your constructor code won't show up in the stack trace. This isn't essential, but it makes things a little less noisy during debugging.&lt;/p&gt;

&lt;p&gt;You can take this a step further: when you have an assertion function that throws your custom error, you can pass &lt;em&gt;that&lt;/em&gt; function into &lt;code&gt;Error.captureStackTrace&lt;/code&gt; instead of your error's constructor. This makes your assertion-caused traces easier to read by starting the trace right where the assertion was called.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Since this API is only available in V8, you'll want to wrap it in an &lt;code&gt;if&lt;/code&gt; or use optional chaining (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining"&gt;the &lt;code&gt;?.&lt;/code&gt; operator&lt;/a&gt;) for compatibility.)&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&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="nx"&gt;asserter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// (Now that we're overriding the parent `Error` &lt;/span&gt;
    &lt;span class="c1"&gt;//  constructor, we have to call `super`!)&lt;/span&gt;
        &lt;span class="k"&gt;super&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="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;captureStackTrace&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;asserter&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="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why write custom Error classes?
&lt;/h2&gt;

&lt;p&gt;JavaScript runtimes provide a base &lt;code&gt;Error&lt;/code&gt; class, plus some more-specific classes that extend that base class (like &lt;code&gt;SyntaxError&lt;/code&gt; and &lt;code&gt;TypeError&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;You can differentiate between these kinds of errors using the &lt;code&gt;instanceof&lt;/code&gt; operator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// do something naughty...&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;err&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="c1"&gt;// There's no way to know ahead of time what caused `err`, so&lt;/span&gt;
    &lt;span class="c1"&gt;// you can use `instanceof` to help narrow down what happened.&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;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;SyntaxError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// do whatever makes sense in that case&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;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;TypeError&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;span class="c1"&gt;// etc etc&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The more information you can get from the caught error, the easier it is to handle it properly without creating &lt;em&gt;even more&lt;/em&gt; problems.&lt;/p&gt;

&lt;p&gt;To that end, custom error classes let you control &lt;em&gt;exactly&lt;/em&gt; what information is available during error handling, and can provide type information to reduce the chances that you create new errors during error-handling. Plus, you can add all kinds of bells and whistles to make them even &lt;em&gt;more&lt;/em&gt; useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Case study: &lt;code&gt;ENOENT&lt;/code&gt; in Node's &lt;code&gt;fs&lt;/code&gt; module
&lt;/h3&gt;

&lt;p&gt;In Node, if you try to &lt;code&gt;fs.readFileSync()&lt;/code&gt; a non-existent file, an error is thrown. If you catch that error and inspect it, you'll see that it's a regular old &lt;code&gt;Error&lt;/code&gt; instance (i.e. &lt;em&gt;not&lt;/em&gt; from a child class) but has had several new fields added to it: &lt;code&gt;errno&lt;/code&gt;, &lt;code&gt;syscall&lt;/code&gt;, &lt;code&gt;code&lt;/code&gt;, and &lt;code&gt;path&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt;ing code that might throw such an error, you have to handle it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fake-file.nope&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;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ENOENT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="c1"&gt;// Then this was a non-existent file error, handle accordingly!&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even once you've narrowed down that the caught error has &lt;code&gt;err.code === ENOENT&lt;/code&gt;, that doesn't tell us anything else about the error instance we've caught. We have to do the same kinds of laborious checks for any other field we want to check.&lt;/p&gt;

&lt;p&gt;So not only is it clunky, it's also easy to create &lt;em&gt;more errors&lt;/em&gt; from inside your &lt;code&gt;catch&lt;/code&gt; block!&lt;/p&gt;

&lt;p&gt;However, if Node instead used a custom &lt;code&gt;FileError&lt;/code&gt; class (for example), we'd only need to do &lt;em&gt;one&lt;/em&gt; check and then our editor would immediately know about all other non-standard error fields provided by the custom class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FileError&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./fs-wrapper-with-custom-errors&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fake-file.nope&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;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;FileError&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="c1"&gt;// Then we already know that this instance will have the&lt;/span&gt;
        &lt;span class="c1"&gt;// `errno`, `syscall`, `code`, and `path` fields, so we can&lt;/span&gt;
        &lt;span class="c1"&gt;// get right into the handling logic!&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Case Study: Uncaught errors in Express.js
&lt;/h3&gt;

&lt;p&gt;The super-popular Node.js server library, &lt;a href="https://expressjs.com/"&gt;Express&lt;/a&gt;, has a powerful feature: you can add a route whose whole job is to &lt;a href="https://expressjs.com/en/guide/error-handling.html"&gt;handle errors thrown inside your other routes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Used in combination with custom error classes, you can simplify a lot of otherwise-cumbersome error handling. For example, instead of having every route have its own logic for handling 404s, via its own try/catch blocks, you can use a custom error to put all of that logic in one place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotFoundError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;assertFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;thing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;asserts&lt;/span&gt; &lt;span class="nx"&gt;thing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;thing&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;NotFoundError&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/some-route&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;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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;something&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getThingIfItExists&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;assertFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;something&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// throws if not found!&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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;something&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&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="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NotFoundError&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Time to make your own!
&lt;/h2&gt;

&lt;p&gt;Now that we've covered the hows and whys of custom JavaScript Error classes, we just need it to be &lt;em&gt;easy&lt;/em&gt;. To that end, the remaining sections provide templates and VSCode snippets for making custom Error classes in JavaScript and Typescript.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Error Class Template (JavaScript)
&lt;/h3&gt;

&lt;p&gt;Here's the JavaScript template, complete with full JSDocs so you can still get type support from your IDE. You can paste this into your code, find-replace "My" with whatever name you want, and you're good to go (though using the VSCode snippet would be less tedious!).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * @param {string} message The error message
     * @param {Function} [asserter] The assertion function that threw the error. Removes stack-trace noise if provided.
     */&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&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="nx"&gt;asserter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&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="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;captureStackTrace&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;asserter&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="kd"&gt;constructor&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="cm"&gt;/**
 * @param {any} err
 * @returns {err is MyError}
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isMyError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;MyError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Assert a condition and throw a MyError if the condition is not met
 * @param {any} condition The condition to assert
 * @param {string} message The error message
 * @param {any} [cause] The cause of the error
 * @returns {asserts condition}
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;assertMyClaim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;condition&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="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MyError&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="nx"&gt;assertMyClaim&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;cause&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cause&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a VSCode snippet to make it really easy:&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;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;snippets&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Create custom JavaScript Error"&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;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"javascript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error-custom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"body"&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;"export class ${1:Custom}Error extends Error {"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&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;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt; * @param {string} message The error message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt; * @param {Function} [asserter] The assertion function that threw the error. Removes stack-trace noise if provided."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&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;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;constructor(message, asserter) {"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="s2"&gt;super(message);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="s2"&gt;Error.captureStackTrace?.(this, asserter || this.constructor);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&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;"}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&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;"/**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;" * @param {any} err"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;" * @returns {err is ${1:Custom}Error}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&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;"export function is${1:Custom}Error(err) {"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;return err instanceof ${1:Custom}Error;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&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;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&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;" * Assert a condition and throw a ${1:Custom}Error if the condition is not met"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;" * @param {any} condition - The condition to assert"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;" * @param {string} message - The error message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;" * @param {any} [cause] - The cause of the error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;" * @returns {asserts condition}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&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;"export function assert${1:Custom}Claim(condition, message, cause) {"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;if (!condition) {"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="s2"&gt;const err = new ${1:Custom}Error(message, assert${1:Custom}Claim);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="s2"&gt;if(cause){"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t\t\t&lt;/span&gt;&lt;span class="s2"&gt;err.cause = cause;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="s2"&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;"&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="s2"&gt;throw err;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&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;"}"&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;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Create a custom JavaScript error."&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;
  
  
  Custom Error Class Template (Typescript)
&lt;/h3&gt;

&lt;p&gt;The Typescript version of the template is exactly the same -- it just moves type annotations from JSDocs into Typescript proper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * @param asserter The assertion function that threw the error. Removes stack-trace noise if provided.
     */&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;asserter&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&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="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;captureStackTrace&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;asserter&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="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isMyError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;MyError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;MyError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * @param cause The cause of the error, e.g. a different, caught error
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;assertMyClaim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;asserts&lt;/span&gt; &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MyError&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="nx"&gt;assertMyClaim&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;cause&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cause&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And again, you &lt;em&gt;could&lt;/em&gt; copy-paste this into your code and find-replace "My" with the name you want. But it'll be a lot easier to just add the following snippet to VSCode:&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;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;snippets&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Create custom Typescript Error"&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;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"typescript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error-custom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"body"&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;"export class ${1:Custom}Error extends Error {"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&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;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt; * @param asserter The assertion function that threw the error. Removes stack-trace noise if provided."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&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;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;constructor(message: string, asserter?: Function) {"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="s2"&gt;super(message);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="s2"&gt;Error.captureStackTrace?.(this, asserter || this.constructor);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&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;"}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&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;"export function is${1:Custom}Error(err: any): err is ${1:Custom}Error {"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;return err instanceof ${1:Custom}Error;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&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;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&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;" * @param cause The cause of the error, e.g. a different, caught error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&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;"export function assert${1:Custom}Claim("&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;condition: any,"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;message: string,"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;cause?: any,"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"): asserts condition {"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;if (!condition) {"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="s2"&gt;const err = new ${1:Custom}Error(message, assert${1:Custom}Claim);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="s2"&gt;if (cause) {"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t\t\t&lt;/span&gt;&lt;span class="s2"&gt;err.cause = cause;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="s2"&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;"&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="s2"&gt;throw err;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&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;"}"&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;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Create a custom Typescript Error."&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;



</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>node</category>
    </item>
    <item>
      <title>HTTP Guide</title>
      <dc:creator>Adam Coster</dc:creator>
      <pubDate>Mon, 03 Jul 2023 21:14:52 +0000</pubDate>
      <link>https://dev.to/adamcoster/http-guide-4kbh</link>
      <guid>https://dev.to/adamcoster/http-guide-4kbh</guid>
      <description>&lt;p&gt;HTTP (Hyper Text Transfer Protocol) is the main way you get information from other people's computers (a.k.a. "the cloud", a.k.a. "the Internet"). It's how browsers fetch web pages, and how automated systems talk to web services.&lt;/p&gt;

&lt;p&gt;Here we'll be talking about HTTP in a practical way, under the assumption that you don't care about the technical details and need to know just enough to be empowered to make use of HTTP in your automation toolkit.&lt;/p&gt;

&lt;p&gt;There are lots of good sources out there for &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages"&gt;more technical details&lt;/a&gt; if you want to dig deeper, but this should be a solid start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Client &amp;amp; Server
&lt;/h2&gt;

&lt;p&gt;There's no way around some of the HTTP jargon since you'll see them out in the wild and need to know what they mean. Two of the biggies are "Client" and "Server" and their near-synonyms.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Client&lt;/strong&gt;: An entity that wants to talk to some other entity (the Server), and thus initiates "requests" to it. The word "request" is perfect since Clients don't get to make &lt;em&gt;demands&lt;/em&gt;. They ask politely, and the Server decides what happens. "Client-side" is an oft-used adjective to describe things happening on the Client. Examples of clients: browsers, games, desktop apps that use a web service, apps on your phone, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server&lt;/strong&gt;: The entity that a Client makes requests to, and that might send responses back (if it feels like it). HTTP servers are software applications that know how to listen for, parse, and respond to HTTP requests from clients. "Server-side" is an oft-used adjective to describe things happening on the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local&lt;/strong&gt;: You'll sometimes see "local" or "locally" used as a synonym for "client". Seen in phrases like, "the server doesn't process everything for you, you'll have to finish up &lt;em&gt;locally.&lt;/em&gt;"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote&lt;/strong&gt;: You'll sometimes see "remote" (meaning "distant", as in "remote control") used as a synonym for "server". This creates symmetry with "local" referring to the client, and makes sense since &lt;em&gt;usually&lt;/em&gt; the servers we care about are &lt;em&gt;over there somewhere&lt;/em&gt; (remote) while the clients we care about are &lt;em&gt;right here in front of us&lt;/em&gt; (local).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The directionality here is important: HTTP requests &lt;em&gt;are initiated by the client&lt;/em&gt; and the server &lt;em&gt;sends responses&lt;/em&gt; back. Servers do not send requests to Clients (by definition). Not over HTTP, anyway.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠ These terms are overloaded so it can get confusing. People often use "the client" to refer to the &lt;em&gt;computer&lt;/em&gt; or even the &lt;em&gt;person&lt;/em&gt; initiating the HTTP request, even though the real client is the &lt;em&gt;application&lt;/em&gt; running on that computer. Similarly, you'll often see "the server" used to refer to the &lt;em&gt;computer&lt;/em&gt; instead of the &lt;em&gt;server application&lt;/em&gt; running on that computer. To add even more confusion, you can run client and server applications &lt;em&gt;on the same machine&lt;/em&gt; -- in that case the terms "local" and "remote" can still be used but now make less obvious sense.&lt;/p&gt;

&lt;p&gt;⚠ Client-Server relationships exist for all kinds of other "protocols" (ways of sending and receiving data over the Internet, one of which being HTTP), but here we're just talking about HTTP.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Requests &amp;amp; Responses
&lt;/h2&gt;

&lt;p&gt;HTTP is all about "Client Requests" and subsequent "Server Responses". Skipping the technical bits, the flow of an HTTP loop looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client composes a request for some particular goal&lt;/li&gt;
&lt;li&gt;Client sends that request to a Server&lt;/li&gt;
&lt;li&gt;Server receives that request and parses its contents (if it wants to)&lt;/li&gt;
&lt;li&gt;Server composes a response based on the contents of the request (if it feels so inclined)&lt;/li&gt;
&lt;li&gt;Server sends the Response to the Client. (Server may instead choose to not respond at all!)&lt;/li&gt;
&lt;li&gt;Client receives the Response and parses its contents&lt;/li&gt;
&lt;li&gt;Client performs some task based on the Response contents&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, let's take an imagined Weather App (the Client) and its Server:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;App gets launched by the user and uses the device's GPS to find the user's location, clock to get the time, and file system to load the last retrieved forecast. It sees that it's been a while since App fetched updated weather info for this location, so it creates a request to get the updated forecast (the request includes the user's location, and any other info it wants to send to the server)&lt;/li&gt;
&lt;li&gt;App sends the request to the Weather server&lt;/li&gt;
&lt;li&gt;Server receives the request and parses its contents&lt;/li&gt;
&lt;li&gt;Server checks the request for information about the user and sees that they have purchased the $9.99/month "In-Depth-Weather Plan". So it fetches to-the-minute forecast data for the provided location and creates a response that includes this data in the body. (Maybe it also stores all the device and user information sent to it, so that the company selling the app can sell that data later... but that's a different topic!)&lt;/li&gt;
&lt;li&gt;Server sends the response to App&lt;/li&gt;
&lt;li&gt;App receives the response and parses it.&lt;/li&gt;
&lt;li&gt;App sees that the data is the In-Depth type, so it runs local code to render a per-minute graph on the user's screen.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Anatomy
&lt;/h3&gt;

&lt;p&gt;What does it mean to "compose" a request or response? What do these things look like?&lt;/p&gt;

&lt;p&gt;While there are nuances and technical details, you can think of both HTTP requests and responses as boring old text files with three main sections:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start Line&lt;/strong&gt; (for requests) or &lt;strong&gt;Status Line&lt;/strong&gt; (for responses). This line provides enough information to indicate that the rest of the content consists of an HTTP request/response, plus some summary info.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headers&lt;/strong&gt;. These are &lt;code&gt;Key: Value&lt;/code&gt; pairs the contain metadata about the request/client or response/server. Request headers often include things like what filetypes the client can understand, the preferred human languages of the client user, and credentials for a logged-in user. Both Request and Response headers will contain information about the Body section (if there is one), such as its type, size, and encoding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Body&lt;/strong&gt; (Optional). Some sort of data being sent to (requests) or received from (responses) the server. For example, the content of a PNG you're uploading or an HTML document you're downloading. Many of the Headers are used to provide information about this data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The biggest difference between requests and responses is that first line, the rest is minor details. So let's see how those first lines compare!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Request "Start Line"&lt;/strong&gt;: Consists of an HTTP method (more on that shortly, a URL, and the protocol type and version: &lt;code&gt;METHOD URL HTTP/VERSION&lt;/code&gt;. For example, if I'm uploading an image to &lt;code&gt;myimagehost.example.com/images&lt;/code&gt;, my Start Line might look like this: &lt;code&gt;POST /images HTTP/1.1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response "Status Line"&lt;/strong&gt;: Consists of the protocol type and version, the status code (more on that shortly), and a message about the status: &lt;code&gt;HTTP/VERSION CODE MESSAGE&lt;/code&gt;. This line is used to summarize the outcome of the request in a generic way. For example, a successful image upload might result in a response with status line &lt;code&gt;HTTP/1.1 201 Created&lt;/code&gt;. Failing the upload because I'm not allowed might result in the status line &lt;code&gt;HTTP/1.1 403 Access Denied&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  HTTP Methods
&lt;/h3&gt;

&lt;p&gt;An "HTTP Method" is one of a handful of keywords (like &lt;code&gt;POST&lt;/code&gt; or &lt;code&gt;GET&lt;/code&gt;) that tell the server what kind of action you are trying to take. When describing a request, people often include the method. So they'll say things like "Send a GET Request to &lt;code&gt;example.com/rad-webpage&lt;/code&gt;".&lt;/p&gt;

&lt;p&gt;While there are &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods"&gt;standards and best-practices&lt;/a&gt; for which Method to use for any given purpose, in the real world it's sorta all over the place. There is no Internet Police to enforce how HTTP Methods are used, and there are all kinds of reasons why any given website might use them in a bespoke way. (And not always &lt;em&gt;good&lt;/em&gt; reasons!)&lt;/p&gt;

&lt;p&gt;I've summarized the most common Methods below, including what they're &lt;em&gt;supposed&lt;/em&gt; to be for versus how they're &lt;em&gt;actually used&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET&lt;/code&gt; is for, well, &lt;em&gt;getting&lt;/em&gt; things. It's the method your browser uses when you click a link or type a URL into the address bar. GET requests are not &lt;em&gt;supposed to&lt;/em&gt; include a Body (but the Internet is a silly place). If you want to fetch a webpage, download an image, or "get" anything from the web, you'll probably use a GET request. In practice, you'll sometimes find GET requests used to &lt;em&gt;change&lt;/em&gt; things on the server as well — in those cases the content of the URL should give you a hint about what will happen when you send the request.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST&lt;/code&gt; is for &lt;em&gt;posting&lt;/em&gt; (uploading) things. When you fill out a form on a website and hit that submit button your browser sends a POST request to the server with all the form data. POSTs are supposed to be for &lt;em&gt;adding new things&lt;/em&gt; to a server. In other words, if you sent a POST to upload an image twice in a row, there should be two copies of that image uploaded to the server now. Historically, GET and POST were the only HTTP methods supported out of the box by browsers, so POST tends to get used for all kinds of purposes. It's pretty common to find services that &lt;em&gt;only&lt;/em&gt; use GET and POST requests.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DELETE&lt;/code&gt; is for, you guessed it, deleting things. DELETE requests are not supposed to contain a body — the URL is supposed to point to the thing you want to delete.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PATCH&lt;/code&gt; is for modifying a subset of something. For example, if you want to update the author of a blog post, you might need to send a PATCH request indicating that fact. In practice, people often use &lt;code&gt;PUT&lt;/code&gt; or &lt;code&gt;POST&lt;/code&gt; for this purpose instead.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PUT&lt;/code&gt; is for &lt;em&gt;completely replacing&lt;/em&gt; something that already exists. It's like &lt;code&gt;PATCH&lt;/code&gt;, except you replace the whole thing instead of modifying part of it. Since that's a subtle distinction, and because the world is chaos, you'll often find &lt;code&gt;PATCH&lt;/code&gt; or &lt;code&gt;POST&lt;/code&gt; used instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  HTTP Status Codes
&lt;/h3&gt;

&lt;p&gt;An "HTTP Status Code" is intended to give a rough idea of why a request succeeded or failed, and therefore hint at what might be found in the body (if anything).&lt;/p&gt;

&lt;p&gt;As with HTTP Methods, there are &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status"&gt;standards and best-practices&lt;/a&gt; for which Status Codes to use under any given scenario. But, also as with Methods, in the real world it's a lot more chaotic than that. I've summarized the most common status codes below, and what you might see out in the wild:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;200&lt;/code&gt; OK. This is the server giving you a thumbs-up and saying that it understood what you were trying to do. So if you got a 200 then you got what you wanted, right? NOPE! There's nuance here, on top of the fact that the person who wrote the server logic might have just chosen to send a confusing status code. Some servers &lt;em&gt;only&lt;/em&gt; reply with 200 codes and provide details in the body! An example of the nuance is: what if your request was a search, and there were no results? The server could send back a 200 with an empty list of results, to indicate that the search ran without error but found nothing. But it could also send a 404 status without any results to indicate the same thing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;201&lt;/code&gt; Created. This is supposed to mean that you uploaded something new (like an image) and that thing now exists. You'll often see 200 used instead.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;204&lt;/code&gt; No Comment. A 204 indicates that the server did what you asked, but doesn't have anything to send you back (no response body). You'll often see 200 used instead, and even see 204s that &lt;em&gt;do&lt;/em&gt; include a body!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2XX&lt;/code&gt; There are other 200-level codes that mean various things, but they aren't very common. You can usually assume that things happened as intended if you get a 200-299 status code.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;3XX&lt;/code&gt; Are various forms of "Go look over there instead!" (a.k.a. "redirects"). You'll get these if something's URL changed but the server knows about the old URL, or if the client already has the thing it's trying to get and that thing hasn't changed on the server.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;400&lt;/code&gt; Whoopsy! All &lt;code&gt;4XX&lt;/code&gt; status are some form of "You messed something up, you silly client!" 400 is the most generic one. It's often used as a catch-all for every kind of client error, so you'll typically need to look for an explanation in the response body or headers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;401&lt;/code&gt; Bad Credentials. You should only get this if your request included access credentials (like a password) but they are somehow invalid (expired or malformed, typically), or if you are trying to connect to a password-protected server without a server password.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;403&lt;/code&gt; Access Denied. This is the server flipping you the bird and saying, "Nope, not today, Satan!" You'll get this for protected actions if you have insufficient permission. It's similar to 401, and sometimes used instead, but is supposed to mean "There's nothing wrong with your credentials (or lack thereof) but &lt;em&gt;you&lt;/em&gt; are not allowed to do this thing you're trying to do."&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;404&lt;/code&gt; Not Found. The old classic. You've probably heard of it. It's supposed to mean that the action you wanted to take on a thing is impossible because the thing doesn't exist. It is also used for the more subtle, "That thing doesn't exist &lt;em&gt;as far as you're concerned&lt;/em&gt;". For example, if I request something that &lt;em&gt;does&lt;/em&gt; exist, but my user isn't allowed to access that thing, then I could either get a 403 ("you aren't allowed to see that") or a 404 ("as far as you're concerned, that thing doesn't exist").&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bringing it all together
&lt;/h3&gt;

&lt;p&gt;In practice, you &lt;em&gt;will not be sending or receiving raw requests&lt;/em&gt;. You'll be using tools that end up doing that for you, and that abstract away the details and make it easier for you to add all the information required for sending a request or processing a response. Still, it's useful to see what the raw stuff looks like because it's simpler than you'd expect and it will help you understand what your tools are doing.&lt;/p&gt;

&lt;p&gt;Let's look at some (fake) examples of raw requests and responses that make use of all the stuff we talked about above. We'll use the Weather App and its Server from before:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user boots up the App, which checks to see if the user has purchased any IAPs by requesting that info from the App's Server (via the URL &lt;code&gt;http://fake-weather-app.example.com/purchases&lt;/code&gt;). The &lt;strong&gt;request&lt;/strong&gt; might look like this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  GET /purchases HTTP/1.1
  Host: fake-weather-app.example.com
  Accept: application/json
  Authentication: Basic SomeCredentialsProvingUserIdentity
  Agent: Fake Weather App/v11.0.23 Android/Google Play
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The Server receives the request, checks the Authentication to make sure it's legit, adds info to its logs, and then finally checks its database for purchases made by this user. It doesn't find any! It then sends a &lt;strong&gt;response&lt;/strong&gt; that might look like this (&lt;a href="https://adam-coster.pages.dev/blog/json-guide"&gt;learn about JSON&lt;/a&gt;):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  HTTP/1.1 200 OK
  Content-Type: application/json; utf-8

  {"purchases":[]}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Thus ends the first request-response cycle! The App sees that the user hasn't made any purchases, and then prompts them to visit the in-app store to see the options. The users does this, and is intrigued 🤔&lt;/li&gt;
&lt;li&gt;The App user purchases the "In-Depth Forecast" IAP bundle, which eventually results in that purchase's info being POSTed to the App's Server with the following &lt;strong&gt;request&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  POST /purchases HTTP/1.1
  Host: fake-weather-app.example.com
  Content-Type: application/json: utf-8
  Accept: application/json
  Authentication: Basic SomeCredentialsProvingUserIdentity
  Agent: Fake Weather App/v11.0.23 Android/Google Play

  {
    "new_purchases": [
      {
        "iap_id": "blahblahblah",
        "paid_amount": 29.99,
        "receipt": "RandomLookingStuffProvingThePurchaseHappened"
      }
    ]
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The App's Server receives this, checks the token, adds this purchase to the database, and &lt;strong&gt;responds&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  HTTP/1.1 201 OK
  Content-Type: application/json; utf-8

  {
    "purchases":[
      {
        "iap_id": "blahblahblah"
      }
    ]
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The App receives this, parses it, and since it &lt;em&gt;trusts its Server&lt;/em&gt; it flips a switch for that bundle so that now every time it checks to see if the user owns that bundle, the answer is &lt;em&gt;yes&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In real life, you'll be using tools with varying complexity and features, like libraries in your favorite programming language or applications built specifically for sending raw requests and receiving raw responses.&lt;/p&gt;

&lt;p&gt;Tools like these will let you separately handle bodies and headers, automatically parse out status codes from responses, and automatically parse response bodies. All the parts will be there, and you'll need to know what to do with all the parts, but you'll get lots of help from your tool-friends.&lt;/p&gt;

&lt;p&gt;If you want to dig deeper, I recommend using &lt;a href="https://www.postman.com/"&gt;Postman&lt;/a&gt; to send requests to real websites and see what they send back, and using your browser's developer tools to see what your browser sends and receives when you visit websites.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>JSON Guide</title>
      <dc:creator>Adam Coster</dc:creator>
      <pubDate>Mon, 03 Jul 2023 21:11:09 +0000</pubDate>
      <link>https://dev.to/adamcoster/json-guide-27mn</link>
      <guid>https://dev.to/adamcoster/json-guide-27mn</guid>
      <description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/JSON"&gt;JSON&lt;/a&gt; (JavaScript Object Notation) is a super popular way of representing complex data for several reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's &lt;em&gt;precise&lt;/em&gt; and &lt;em&gt;unambiguous&lt;/em&gt; (so computers can read it)&lt;/li&gt;
&lt;li&gt;It's not &lt;em&gt;too&lt;/em&gt; verbose (so humans can read it)&lt;/li&gt;
&lt;li&gt;It's simple (so humans can learn it)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How does JSON work?&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Types &amp;amp; Format
&lt;/h2&gt;

&lt;p&gt;JSON can be used to represent data structures composed of the following kinds of data subtypes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Number&lt;/strong&gt;: A number! Just like you'd expect&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boolean&lt;/strong&gt;: &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;String&lt;/strong&gt;: Some text, always wrapped in double-quotes, like &lt;code&gt;"hello"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Null&lt;/strong&gt;: &lt;code&gt;null&lt;/code&gt;, which is a sort of "empty" value. Interpretation depends on context, but it usually indicates the &lt;em&gt;absence&lt;/em&gt; of something.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Array&lt;/strong&gt;: A list of data (of any JSON-compatible type), separated by commas with the whole thing wrapped in square brackets, e.g. &lt;code&gt;["hello", -10.3, null, ["another array"], {"a key":"for an object"}, false]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object&lt;/strong&gt;: A collection of keys (always strings) and corresponding values (any JSON data type), represented as comma-separated key-value pairs, with colons between each key and its value, all surrounded by a pair of curly brackets, e.g. &lt;code&gt;{"this is a key": "this is a value", "next key":["value can be arrays",10], "yes":true}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Looking at those examples, you might be thinking, "Whoa, that's really hard to read." You're not wrong!&lt;/p&gt;

&lt;p&gt;JSON allows you to use however much whitespace you want between &lt;em&gt;separators&lt;/em&gt;, &lt;em&gt;values&lt;/em&gt;, and &lt;em&gt;brackets&lt;/em&gt;. If we use good spacing plus a dash of syntax highlighting, it gets a lot better. Here's that last example again but nicely formatted:&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;"this is a key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"this is a value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"next key"&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;"value can be arrays"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="mi"&gt;10&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;"yes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;JSON is extremely strict. If you forget a comma (or have one too many), or use single-quotes instead of double-quotes for your strings, it'll be considered invalid and most parsers will throw errors. &lt;/p&gt;

&lt;h2&gt;
  
  
  JSON Interpretation
&lt;/h2&gt;

&lt;p&gt;Well-written JSON &lt;em&gt;describes itself&lt;/em&gt;, but it's pretty rare that something is so self-descriptive that you don't need to look for documentation or do experiments.&lt;/p&gt;

&lt;p&gt;Typically, JSON "Objects" are used to represent instances of things that have &lt;em&gt;properties&lt;/em&gt;. For example, a "user" may have a name, age, email address, password, and a jillion other possible properties. You might represent all that with an Object like so:&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;"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;"Vlad"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vlad@impaler.blood"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"isVampire"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;An Array is typically used to contain a set of related values, though that isn't strictly required. A common use case is getting search results from a web service, where you'll end up with more than one result for the same sort of thing.&lt;/p&gt;

&lt;p&gt;For example, let's say you fetch the list of users from a service. That list might look something like:&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="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;"Adam"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adam@example.com"&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;"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;"Vlad"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vlad@impaler.blood"&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;"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;"Susan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"susan@example.com"&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;p&gt;While you'll frequently see things like lists of objects, it is rare that you'll get them like in the above example without any &lt;em&gt;metadata&lt;/em&gt; (data about data).&lt;/p&gt;

&lt;p&gt;Typically, if you're getting JSON data from some source it will be an Object whose root keys are for both metadata and the actual data itself. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// metadata...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_pages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_returned"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"search_results"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c1"&gt;// actual data we wanted...&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;"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;"Adam"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adam@example.com"&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;"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;"Vlad"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vlad@impaler.blood"&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;"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;"Susan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"susan@example.com"&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;p&gt;Interpreting JSON data is all about understanding where it came from, looking for any standardization of the outputs from that source, knowing what you were trying to obtain, and then figuring out what all of the keys and values mean. It can be tricky business, especially for deeply nested data (objects of arrays of arrays of objects of objects... it can get pretty gnarly!).&lt;/p&gt;

&lt;h2&gt;
  
  
  Special Characters, Escape!
&lt;/h2&gt;

&lt;p&gt;I noted above that strings are always wrapped in double-quotes. What if you want to put a double-quote inside of a string?&lt;/p&gt;

&lt;p&gt;There are other special cases, too. What if you want to put a newline or a tab in a string?&lt;/p&gt;

&lt;p&gt;Double-quotes are "special characters." They're special because they don't simply indicate a text character, they have a &lt;em&gt;function&lt;/em&gt;. They are providing instructions to parsers.&lt;/p&gt;

&lt;p&gt;For example, a boring old non-special &lt;code&gt;R&lt;/code&gt; means "I'm the letter 'R'" while the special &lt;code&gt;"&lt;/code&gt; means, "A string is starting or ending!" See that difference? The &lt;code&gt;"&lt;/code&gt; is &lt;em&gt;functional&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Newlines are a related, but different problem. How do you represent a newline in text? You might think that you can just hit the ENTER key and be done with it. Not a bad guess, but it doesn't work that way for JSON! If you slap a newline into a string via the ENTER key, JSON gets stuck at that newline and assumes you forgot an end-quote, and then throws errors. Oops.&lt;/p&gt;

&lt;p&gt;To represent newlines without using newlines, you have to &lt;em&gt;make a regular character special&lt;/em&gt;. It's sorta the opposite of the double-quotes case, where we made a special character more regular.&lt;/p&gt;

&lt;p&gt;There is a special character you can use inside JSON strings that basically toggles special-ness. It says, "If the next character is already a special character, make it a regular character. But if it's &lt;em&gt;not&lt;/em&gt; a special character, make it one!"&lt;/p&gt;

&lt;p&gt;We call this special character the "escape character". Why? I guess because it lets you escape from the normal state of the world? Who knows. The point is that the escape character inverts the specialness of the next character. JSON's escape character is &lt;code&gt;\&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Side note: This whole "escape character" thing is quite common in text programming and data representation, and the &lt;code&gt;\&lt;/code&gt; character is probably the most common character used for this purpose.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So if you want to use a double-quote &lt;em&gt;inside&lt;/em&gt; a string, you need to differentiate it from a double-quote that would &lt;em&gt;end&lt;/em&gt; the string. You do this by escaping it: &lt;code&gt;\"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want to stick a newline inside a string, instead of hitting that ENTER key you invoke the special form of the letter &lt;code&gt;n&lt;/code&gt;: &lt;code&gt;\n&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What if you want to use a literal &lt;code&gt;\&lt;/code&gt; character inside a string? You guessed it: &lt;code&gt;\\&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;(There are other special characters in JSON, but mostly you'll only care about double-quotes, newlines, and tabs.)&lt;/p&gt;

&lt;p&gt;For example:&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;"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;"Adam"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"me@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Adam is writing stuff about JSON.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;See that &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;slash-n&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; right back there? That was a NEWLINE! &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;What about quotes?&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; you ask?&lt;/span&gt;&lt;span class="se"&gt;\n\t&lt;/span&gt;&lt;span class="s2"&gt;Well, you just saw an escaped quote with a newline and tab character after it!"&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;h2&gt;
  
  
  JSON Limitations
&lt;/h2&gt;

&lt;p&gt;While JSON is super popular on the web and elsewhere, it has some significant limitations that make it less than ideal for some use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It only has one kind of "number". In programming, we often want to differentiate between integer ("whole") numbers and floating-point ("decimal") numbers. JSON doesn't have that distinction, so you have to be careful about how you use numbers.&lt;/li&gt;
&lt;li&gt;There are other data types that we very often want to use, like dates and binary, that JSON does not support. Those have to be stored as strings or numbers and converted back and forth as needed. This can turn into quite the headache.&lt;/li&gt;
&lt;li&gt;It's more verbose than is strictly necessary. For example, since the keys in JSON Objects are &lt;em&gt;always&lt;/em&gt; strings, why do we need to wrap them in double-quotes?&lt;/li&gt;
&lt;li&gt;It does not support comments, in the programming sense. In programming, a "comment" is a non-functional bit of text that can be used to provide information to the reader without impacting interpretation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are some extensions to plain JSON, like JSON5 and JSONC, and some libraries that add support for other data types, but those are not natively supported by most software.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSON Tools
&lt;/h2&gt;

&lt;p&gt;If you're trying to read or write JSON, in either case you're going to want some decent tools to catch problems early. It's incredibly easy to miss a special character (or forget to escape one), especially if you don't have good indentation and syntax highlighting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://code.visualstudio.com/"&gt;VSCode&lt;/a&gt; is always a great go-to for dealing with JSON. You can edit JSON files directly with it, or create a new tab Ctrl + N and paste some JSON into it (then set the language to JSON for that tab). You'll get syntax highlighting out of the box, and VSCode will highlight errors and try to autocomplete brackets and quotes for you! You can even get auto-formatters (also called "prettifiers") that will fix all the indentation for you, so you can dump a wall of spaceless nightmare-JSON and then let robots make it readable for you.&lt;/p&gt;

&lt;p&gt;If you don't have VSCode ready to go for JSON, or are otherwise in a pinch, there are tons of &lt;a href="https://geekflare.com/json-online-tools/"&gt;online JSON tools&lt;/a&gt; out there.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>The "Can We Not" Principle</title>
      <dc:creator>Adam Coster</dc:creator>
      <pubDate>Mon, 03 Jul 2023 21:08:41 +0000</pubDate>
      <link>https://dev.to/adamcoster/the-can-we-not-principle-4jmi</link>
      <guid>https://dev.to/adamcoster/the-can-we-not-principle-4jmi</guid>
      <description>&lt;p&gt;One of the most important pillars I use to guide my decisions is the "Can we not?" principle. It's a simple idea: if I can avoid doing something, I should.&lt;/p&gt;

&lt;p&gt;Sure, you do have to do &lt;em&gt;some&lt;/em&gt; things to, say, build and sell a product or manage a household. But things you've done are also the source of future problems. Code we write has to be maintained. Features we build have to be supported.&lt;/p&gt;

&lt;p&gt;The more we do, the more we have to do. And this just keeps adding up -- what is an organization or household but an ever-growing collection of things that have to be maintained?&lt;/p&gt;

&lt;p&gt;Conversely, any time you &lt;em&gt;don't&lt;/em&gt; do something you now have one fewer thing to deal with tomorrow. That is, if that thing you didn't do today is also something you don't &lt;em&gt;have&lt;/em&gt; to do tomorrow.&lt;/p&gt;

&lt;p&gt;Some things can't be avoided. But far more things can be avoided than we typically think. It's always worth asking the question: "Can we not?"&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>devops</category>
    </item>
    <item>
      <title>Configure pnpm for the best possible developer experience</title>
      <dc:creator>Adam Coster</dc:creator>
      <pubDate>Mon, 29 May 2023 18:57:28 +0000</pubDate>
      <link>https://dev.to/adamcoster/configure-pnpm-for-the-best-possible-developer-experience-a17</link>
      <guid>https://dev.to/adamcoster/configure-pnpm-for-the-best-possible-developer-experience-a17</guid>
      <description>&lt;p&gt;I've spent a lot of time with the big-three Node.js package managers (npm, pnpm, and yarn), and I've consistently found pnpm to deliver the best overall experience. Of particular interest to me is that it works great with "monorepos" (many packages in one git repository).&lt;/p&gt;

&lt;p&gt;While pnpm's defaults are solid, there are a bunch of tweaks that make things even better. After a lot of trial and error I've found the settings that make my development experience great.&lt;/p&gt;

&lt;p&gt;The first sections of this post are for people new to pnpm. For those already familiar, you can jump right to my configuration recommendations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Differences from npm
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pnpm.io/"&gt;pnpm&lt;/a&gt; is largely a drop-in replacement for npm: just plop in a &lt;code&gt;package.json&lt;/code&gt; file and start using &lt;code&gt;pnpm &amp;lt;whatever&amp;gt;&lt;/code&gt; commands!&lt;/p&gt;

&lt;p&gt;There are some differences that are good to be aware of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monorepos are specified using a &lt;code&gt;pnpm-workspace.yaml&lt;/code&gt; file instead of the &lt;code&gt;"workspaces"&lt;/code&gt; field in &lt;code&gt;package.json&lt;/code&gt; that npm and yarn use.&lt;/li&gt;
&lt;li&gt;You can run all of your &lt;code&gt;package.json&lt;/code&gt; scripts via &lt;code&gt;pnpm &amp;lt;script-name&amp;gt;&lt;/code&gt; (npm lets you do that for some scripts, but others require the &lt;code&gt;npm run&lt;/code&gt; subcommand)&lt;/li&gt;
&lt;li&gt;You can also run your installed binaries with &lt;code&gt;pnpm &amp;lt;command&amp;gt;&lt;/code&gt;. For example, you might want to run local versions of Typescript (&lt;code&gt;pnpm tsc&lt;/code&gt;) or eslint (&lt;code&gt;pnpm eslint&lt;/code&gt;). npm uses a separate command (&lt;code&gt;npx&lt;/code&gt;) to run binaries.&lt;/li&gt;
&lt;li&gt;pnpm separates the installing the dependencies listed in your &lt;code&gt;package.json&lt;/code&gt; (via the command &lt;code&gt;pnpm install&lt;/code&gt;) from adding new dependencies (via the command &lt;code&gt;pnpm add&lt;/code&gt;). npm uses &lt;code&gt;npm install&lt;/code&gt; for both.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To add a local dependency within a monorepo, in your &lt;code&gt;package.json&lt;/code&gt; &lt;code&gt;"dependencies"&lt;/code&gt; field you'll prefix your local dependencies' version-range strings with &lt;code&gt;workspace:&lt;/code&gt; (a.k.a. the "workspace protocol"). When publishing, pnpm resolves those prefixed versions to their actual versions in the published packages. For example, if I have package &lt;code&gt;a&lt;/code&gt; depending on &lt;code&gt;b&lt;/code&gt; in my monorepo, the &lt;code&gt;package.json&lt;/code&gt; for &lt;code&gt;a&lt;/code&gt; would look like:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"workspace:*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// (where '*' means "any version")&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;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are many other differences, but the above are the most likely to trip you up when switching from npm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing pnpm
&lt;/h2&gt;

&lt;p&gt;I recommend &lt;a href="https://dev.to/blog/corepack-ensures-consistent-pnpm-version"&gt;using corepack&lt;/a&gt; since that keeps things nice and automagic, but there are also &lt;a href="https://pnpm.io/installation"&gt;other ways&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic setup for monorepos
&lt;/h2&gt;

&lt;p&gt;There is only one thing you &lt;em&gt;need&lt;/em&gt; to do to enable pnpm for your monorepo: add a &lt;code&gt;pnpm-workspace.yaml&lt;/code&gt; file alongside your root &lt;code&gt;package.json&lt;/code&gt;. It should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The only field in this config file!&lt;/span&gt;
&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Add paths to each package's folder, or glob patterns&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;packages/*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Now you're ready to use pnpm. BUT, with some additional configuration you can get a much slicker dev experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switching from npm
&lt;/h2&gt;

&lt;p&gt;While the switch from npm to pnpm is pretty smooth, there are some changes you'll have to make:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nuke all &lt;code&gt;node_modules&lt;/code&gt; folders with &lt;code&gt;rm -rf **/node_modules&lt;/code&gt;. pnpm stores dependencies completely differently and will need to download everything fresh.&lt;/li&gt;
&lt;li&gt;In your &lt;code&gt;package.json&lt;/code&gt; files, update all local dependency listings to use the workspace protocol. For example, &lt;code&gt;{dependencies:{myLocalPackage:"^1.2.3"}}&lt;/code&gt; would become &lt;code&gt;{dependencies:{myLocalPackage:"workspace:*"}}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Find all instances of the words "npm" and "npx" across all projects and replace them with &lt;code&gt;pnpm&lt;/code&gt;. You can use a regex search like &lt;code&gt;\bnp[mx]\b&lt;/code&gt; and restrict the search to appropriate filepaths to do this quickly and with minimal error.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update your VSCode &lt;code&gt;settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//...
"search.exclude": {
  // Avoid polluting search results with lockfile content
  "pnpm-lock.yaml": true,
},
// Ensure VSCode uses pnpm instead of npm
"eslint.packageManager": "pnpm",
"npm.packageManager": "pnpm",
// For those using file-nesting, nest the new files. E.g.:
"explorer.fileNesting.patterns":{
  "package.json": "pnpm-workspace.yaml, pnpm-lock.yaml"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get the best developer experience
&lt;/h2&gt;

&lt;p&gt;The pnpm experience is solid from the jump, but you can make it GREAT by digging into the configuration options. The following are my recommendations for how to configure pnpm in a monorepo context:&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;.npmrc&lt;/code&gt; file
&lt;/h3&gt;

&lt;p&gt;Just like npm, pnpm reads configuration information from &lt;code&gt;.npmrc&lt;/code&gt; files. These files use &lt;code&gt;key=value&lt;/code&gt; pairs to set config options.&lt;/p&gt;

&lt;p&gt;Notably, any configuration settings in your repo-root &lt;code&gt;.npmrc&lt;/code&gt; file will &lt;em&gt;also apply&lt;/em&gt; to all of the packages within your monorepo! This is super useful, since it allows you to centralize your pnpm settings. You can even override settings for a specific package by adding a different &lt;code&gt;.npmrc&lt;/code&gt; alongside that package's &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;.npmrc&lt;/code&gt; file alongside your root &lt;code&gt;package.json&lt;/code&gt; file, and then set the following (see &lt;a href="https://pnpm.io/npmrc"&gt;the docs&lt;/a&gt; for all options):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;use-node-version=18.16.0&lt;/code&gt; (or whatever version you want). This setting causes pnpm to use the listed Node version for its operations, including when you run your &lt;code&gt;package.json&lt;/code&gt; scripts or run node itself (if you do so via &lt;code&gt;pnpm node&lt;/code&gt; instead of calling &lt;code&gt;node&lt;/code&gt; directly). pnpm will even install that version for you if it isn't already available! You can also change this setting for any specific package, allowing you to ensure that your packages are always using the node version you want without you ever having to think about it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strict-peer-dependencies=false&lt;/code&gt; Peer dependencies are a nightmare. I always set this to &lt;code&gt;false&lt;/code&gt; to make them less so.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;publish-branch=main&lt;/code&gt; (or whatever your main git branch is). This prevents &lt;code&gt;pnpm publish&lt;/code&gt; from running if you aren't on the specified branch, which can save you some headaches.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;save-prefix=""&lt;/code&gt;. This makes it so that &lt;em&gt;exact&lt;/em&gt; versions are used when you run &lt;code&gt;pnpm add&lt;/code&gt;. This is a good default since it's the safest, and you can always change the behavior for specific dependencies by manually editing their version (e.g. by changing &lt;code&gt;"1.1.3"&lt;/code&gt; to &lt;code&gt;"^1.1.3"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;link-workspace-packages=false&lt;/code&gt;. Your monorepo packages can depend on each other, but how does pnpm decide if you mean the &lt;em&gt;local&lt;/em&gt; version of a dependency (allowing symlinking right to the source) versus a specific past version (requiring downloading separate files)? pnpm provides a few ways to handle this, but setting this option to &lt;code&gt;false&lt;/code&gt; is the most predictable and explicit. And that means fewer surprises! This option tells pnpm to &lt;em&gt;only&lt;/em&gt; resolve local deps to the local files when the &lt;code&gt;workspace:&lt;/code&gt; protocol is used, and to otherwise download published versions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;save-workspace-protocol=rolling&lt;/code&gt;. This dictates how &lt;code&gt;pnpm add&lt;/code&gt; decides whether or not to include the &lt;code&gt;workspace:&lt;/code&gt; prefix and whether or not the version will be specified. Whatever default you choose, you can easily override a dependency by manually editing its version string in your &lt;code&gt;package.json&lt;/code&gt; dependencies. There's significant nuance in which option to choose for defaults that make sense for your use case:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;false&lt;/code&gt;: Never use the &lt;code&gt;workspace:&lt;/code&gt; prefix. In combination with &lt;code&gt;link-workspace-packages=false&lt;/code&gt; newly-added deps will always resolve to published, &lt;em&gt;static&lt;/em&gt; artifacts (instead of local ones). This yields the fewest surprises, but doesn't let you take full advantage of having all of your packages in one repo.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;true&lt;/code&gt;: Always include the &lt;code&gt;workspace:&lt;/code&gt; prefix &lt;em&gt;and&lt;/em&gt; the version of the dep at the time of &lt;code&gt;pnpm add&lt;/code&gt;, for local dependencies. This is the most balanced choice if you have really good versioning and testing tooling, since you can still do rapid development across dependency boundaries but will get alerted when a dep gets bumped too far.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rolling&lt;/code&gt;: Always include the &lt;code&gt;workspace:&lt;/code&gt; prefix but &lt;em&gt;not&lt;/em&gt; the specific version. This provides the smoothest development experience, since you can freely move between projects and see the consequences on their dependents instantly, without having to deal with semver range conflicts. However, you need robust versioning and testing tooling to stay out of trouble since it's easy to publish something without also publishing its updated dependency, among other issues. This case is best reserved for monorepos where all projects always get version-bumped at the same time, and automatically, if there have been any changes to them.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Manage dependency versions with syncpack and depcheck
&lt;/h2&gt;

&lt;p&gt;pnpm helps to reduce downloads and the size of dependencies on disk by symlinking shared dependencies. However, when your shared dependencies use incompatible versions you lose a lot of those benefits. This is a particular problem when you use the more conservative settings recommended above, like forcing &lt;em&gt;exact&lt;/em&gt; versions of dependencies instead of ranges.&lt;/p&gt;

&lt;p&gt;To mitigate this, I recommend using &lt;a href="https://www.npmjs.com/package/syncpack"&gt;syncpack&lt;/a&gt;. Syncpack looks at all of your &lt;code&gt;package.json&lt;/code&gt; files to find all common dependencies, and ensures they're all at the same version. If you need more nuance than that, it also has a lot of configuration options.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;syncpack fix-mismatches&lt;/code&gt; to the &lt;code&gt;"postinstall"&lt;/code&gt; script in your &lt;code&gt;package.json&lt;/code&gt; to ensure that your dependency versions are being frequently synced.&lt;/p&gt;

&lt;p&gt;Finally, over time you're likely to end up with some stray dependencies that are no longer in use by your packages. &lt;a href="https://www.npmjs.com/package/depcheck"&gt;depcheck&lt;/a&gt; is a great tool for this, and it also has a ton of configuration options.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>javascript</category>
    </item>
    <item>
      <title>JavaScript wants your functions to have names</title>
      <dc:creator>Adam Coster</dc:creator>
      <pubDate>Mon, 29 May 2023 18:54:34 +0000</pubDate>
      <link>https://dev.to/adamcoster/javascript-wants-your-functions-to-have-names-1om9</link>
      <guid>https://dev.to/adamcoster/javascript-wants-your-functions-to-have-names-1om9</guid>
      <description>&lt;p&gt;JavaScript function names can be quite helpful for debugging and logging, but how does JavaScript determine what a function's name &lt;em&gt;is&lt;/em&gt;?&lt;/p&gt;

&lt;h2&gt;
  
  
  JavaScript functions have a &lt;code&gt;name&lt;/code&gt; property
&lt;/h2&gt;

&lt;p&gt;The first thing to note, since it's not at all obvious, is that JavaScript functions are special kinds of &lt;em&gt;objects&lt;/em&gt; with a few default properties:&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;function&lt;/span&gt; &lt;span class="nx"&gt;myFunction&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getOwnPropertyNames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myFunction&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// [ 'length', 'name', 'prototype' ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of those properties is the &lt;code&gt;name&lt;/code&gt; field. That's where your function's name is stored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Function names are &lt;em&gt;readonly&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;If we take a named function and look at its &lt;code&gt;name&lt;/code&gt; property descriptor, we see that it is readonly:&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;function&lt;/span&gt; &lt;span class="nx"&gt;myFunction&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getOwnPropertyDescriptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myFunction&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&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//   value: 'myFunction',&lt;/span&gt;
&lt;span class="c1"&gt;//   writable: false,&lt;/span&gt;
&lt;span class="c1"&gt;//   enumerable: false,&lt;/span&gt;
&lt;span class="c1"&gt;//   configurable: true&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;span class="nx"&gt;myFunction&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;anotherName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// TypeError: Cannot assign to read only property 'name' of function 'function myFunction(){}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the &lt;code&gt;name&lt;/code&gt; property is &lt;em&gt;configurable&lt;/em&gt; by default. So while we can't directly re-assign the name using an assignment operation, we can still use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty"&gt;&lt;code&gt;Object.defineProperty&lt;/code&gt;&lt;/a&gt; to rename a function:&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;function&lt;/span&gt; &lt;span class="nx"&gt;myFunction&lt;/span&gt;&lt;span class="p"&gt;(){};&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myFunction&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&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="na"&gt;writable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myRenamedFunction&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myFunction&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="c1"&gt;// myRenamedFunction&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  JavaScript &lt;em&gt;wants&lt;/em&gt; to name your functions
&lt;/h2&gt;

&lt;p&gt;You can explicitly name a function by using the &lt;code&gt;function myFunctionName(){}&lt;/code&gt; syntax. But when you don't use that syntax, e.g. with arrow functions or with "anonymous" &lt;code&gt;function(){}&lt;/code&gt; declarations, JavaScript will still try to find a name to give to your function.&lt;/p&gt;

&lt;p&gt;If you've declared your function on the right-hand-side of an assignment, the name of the variable on the left is used as the name:&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;myFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){};&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myFunction&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="c1"&gt;// myFunction&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myArrowFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myArrowFunction&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="c1"&gt;// myArrowFunction&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;myMethod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){},&lt;/span&gt;
  &lt;span class="na"&gt;myArrowMethod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myMethod&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="c1"&gt;// myMethod&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myArrowMethod&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="c1"&gt;// myArrowMethod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Do "anonymous" JavaScript functions even exist?
&lt;/h2&gt;

&lt;p&gt;No, not really. If you create an "anonymous" function that also isn't directly assigned to a named variable, you can get an "anonymous" function:&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;anonymousFunctionGenerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myAnonymousFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;anonymousFunctionGenerator&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myAnonymousFunction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// [Function (anonymous)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;BUT! The function &lt;em&gt;does still have a name&lt;/em&gt;, it's just a sneaky one: an empty string!&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;anonymousFunctionGenerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myAnonymousFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;anonymousFunctionGenerator&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getOwnPropertyDescriptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myAnonymousFunction&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&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// { value: '', writable: false, enumerable: false, configurable: true }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>CommonJS and ESM import/export compatibility, by example</title>
      <dc:creator>Adam Coster</dc:creator>
      <pubDate>Thu, 10 Mar 2022 00:56:35 +0000</pubDate>
      <link>https://dev.to/adamcoster/commonjs-and-esm-importexport-compatibility-by-simple-example-50pl</link>
      <guid>https://dev.to/adamcoster/commonjs-and-esm-importexport-compatibility-by-simple-example-50pl</guid>
      <description>&lt;p&gt;Node's &lt;a href="https://nodejs.org/docs/latest-v16.x/api/modules.html"&gt;CommonJS&lt;/a&gt; vs. &lt;a href="https://nodejs.org/docs/latest-v16.x/api/esm.html"&gt;ECMAScript ("ESM")&lt;/a&gt; divide is probably the source of &lt;em&gt;most&lt;/em&gt; of my quality of life frustrations as a fullstack Typescript/Node/Javascript programmer.&lt;/p&gt;

&lt;p&gt;I can often go for weeks at a time before running into new incompatibility problems, so then each time I have to remind myself how interoperability works between them. Well, this time I made a tiny, simple demo so that &lt;em&gt;next&lt;/em&gt; time I can just refer to it. And now you can, too!&lt;/p&gt;

&lt;p&gt;Short summary of the CommonJS/ESM distinction and problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CommonJS uses the &lt;code&gt;require('./file.js')&lt;/code&gt; syntax for importing other modules and the &lt;code&gt;module.exports =&lt;/code&gt; syntax for exporting stuff from modules&lt;/li&gt;
&lt;li&gt;ESM uses the &lt;code&gt;import {stuff} from './file.js'&lt;/code&gt; syntax for importing and the &lt;code&gt;export stuff&lt;/code&gt; syntax for exports&lt;/li&gt;
&lt;li&gt;CommonJS files can use the &lt;code&gt;.cjs&lt;/code&gt; extension to tell Node that they are in CommonJS&lt;/li&gt;
&lt;li&gt;ESM files can use the &lt;code&gt;.mjs&lt;/code&gt; extension to tell Node that they are in ESM&lt;/li&gt;
&lt;li&gt;CommonJS imports are &lt;em&gt;synchronous&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;ESM imports are &lt;em&gt;asynchronous&lt;/em&gt; (which also allows for &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top_level_await"&gt;top-level &lt;code&gt;await&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;CommonJS works in Node but does &lt;strong&gt;not&lt;/strong&gt; work in browsers&lt;/li&gt;
&lt;li&gt;ESM is supported by all modern browsers and the latest versions of Node, but does &lt;strong&gt;not&lt;/strong&gt; work at all in Node versions below 12&lt;/li&gt;
&lt;li&gt;Tons of the core JavaScript ecosystem tooling was developed in Node and Node only recently supported ESM, so a huge fraction of existing Node projects are in CommonJS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So that's our situation. Now, to the problem at hand: If you are using ESM, can you import CommonJS? What about the other way around?&lt;/p&gt;

&lt;p&gt;In short, YES! But with considerations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample export modules
&lt;/h2&gt;

&lt;p&gt;Let's start with some &lt;em&gt;importable&lt;/em&gt; modules. One in CommonJS, the other in ESM:&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="cm"&gt;/**
 * @file `exporter.mjs`
 * (An ESM module exporting a default and named entity.)
 */&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;namedMjsExport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;defaultMjsExport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * @file `exporter.cjs`
 * (A CommonJS module exporting a default and named entity.)
 */&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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;defaultCjsExport&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;namedCjsExport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;namedCjsExport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Importing &lt;em&gt;from&lt;/em&gt; ESM and CommonJS &lt;em&gt;to&lt;/em&gt; ESM
&lt;/h2&gt;

&lt;p&gt;What does it look like to import &lt;em&gt;both&lt;/em&gt; of those modules into another ESM module? Simple! If you are importing &lt;em&gt;into&lt;/em&gt; an ESM module, it looks the same either way:&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="cm"&gt;/**
 * @file `importer.mjs`
 *
 * An ESM module that imports stuff
 */&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;defaultCjsExport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;namedCjsExport&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./exporter.cjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;defaultMjsExport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;namedMjsExport&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./exporter.mjs&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Importing into an ESM module.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;defaultCjsExport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;namedCjsExport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;defaultMjsExport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;namedMjsExport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And after we run that script via &lt;code&gt;node importer.mjs&lt;/code&gt; (Node v16):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  title: 'Importing into an ESM module.',
  defaultCjsExport: [Function: defaultCjsExport] {
    namedCjsExport: [Function: namedCjsExport]
  },
  namedCjsExport: [Function: namedCjsExport],
  defaultMjsExport: [Function: defaultMjsExport],
  namedMjsExport: [Function: namedMjsExport]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect! If we're using ESM we can basically treat &lt;em&gt;all&lt;/em&gt; code as if it's also ESM. (There are some nuances, but we can usually get away with ignoring them.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Importing &lt;em&gt;from&lt;/em&gt; ESM and CommonJS &lt;em&gt;to&lt;/em&gt; CommonJS
&lt;/h2&gt;

&lt;p&gt;So importing into ESM is no biggie, are we so lucky with importing into CommonJS?&lt;/p&gt;

&lt;p&gt;NOPE!&lt;/p&gt;

&lt;p&gt;Since &lt;code&gt;require()&lt;/code&gt; is synchronous, you can't use it to import ESM modules &lt;em&gt;at all&lt;/em&gt;! In CommonJS you have to use &lt;code&gt;require&lt;/code&gt; syntax for other CommonJS modules and an &lt;code&gt;import()&lt;/code&gt; &lt;em&gt;function&lt;/em&gt; (distinct from the &lt;code&gt;import&lt;/code&gt; keyword used in ESM!), a function that returns a &lt;em&gt;promise&lt;/em&gt;, to import ESM.&lt;/p&gt;

&lt;p&gt;Let's take a look:&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="cm"&gt;/**
 * @file `importer.cjs`
 *
 * From a require-style Node script, import cjs and mjs modules.
 */&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Import a module by `require()`ing it. If that results in
 * an error, return the error code.
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;requireModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;exportName&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imported&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;exportName&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;imported&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;exportName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;imported&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;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&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="cm"&gt;/**
 * CommonJS does not have top-level `await`, so we can wrap
 * everything in an `async` IIFE to make our lives a little easier.
 */&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Importing into a CommonJS module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// CJS&amp;lt;-CJS and MJS&amp;lt;-CJS are equivalent&lt;/span&gt;
    &lt;span class="na"&gt;defaultCjsExport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requireModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./exporter.cjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;namedCjsExport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requireModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./exporter.cjs&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;namedCjsExport&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;// Cannot `require` an ESM module&lt;/span&gt;
    &lt;span class="na"&gt;defaultMjsExportUsingRequire&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requireModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./exporter.mjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;namedMjsExportUsingRequire&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requireModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./exporter.mjs&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;namedMjsExport&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;defaultMjsExport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./exporter.mjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;namedMjsExport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./exporter.mjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;namedMjsExport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the output of &lt;code&gt;node importer.cjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  title: 'Importing into a CommonJS module',
  defaultCjsExport: [Function: defaultCjsExport] {
    namedCjsExport: [Function: namedCjsExport]
  },
  namedCjsExport: [Function: namedCjsExport],
  defaultMjsExportUsingRequire: 'ERR_REQUIRE_ESM',
  namedMjsExportUsingRequire: 'ERR_REQUIRE_ESM',
  defaultMjsExport: [Function: defaultMjsExport],
  namedMjsExport: [Function: namedMjsExport]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oh, wow, look at how much more code we needed and how careful we need to be!&lt;/p&gt;

&lt;h2&gt;
  
  
  Advice
&lt;/h2&gt;

&lt;p&gt;I've been all-in on ESM for a while now. It's a better developer experience and is clearly what we'll be using in the future. But it comes with headaches because so much of the Node ecosystem is still in CommonJS, and you should think carefully before going all-in.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't forget about the file extensions! Modern Node handles the &lt;code&gt;.mjs&lt;/code&gt; and &lt;code&gt;.cjs&lt;/code&gt; extensions, so if you need to use one module type in one place and another somewhere else, feel free to mix it up! This also works in Typescript (v4.5+) with the &lt;code&gt;.mts&lt;/code&gt; and &lt;code&gt;.cts&lt;/code&gt; extensions.&lt;/li&gt;
&lt;li&gt;(But also note that some tools don't know about those extensions...)&lt;/li&gt;
&lt;li&gt;Tools written in CommonJS (i.e. &lt;em&gt;most&lt;/em&gt; existing Node-based tools) usually handle ESM poorly. Even extremely popular projects. If you want to guarantee that you can use a tool with your code, you may want to stick with CommonJS.&lt;/li&gt;
&lt;li&gt;If you will mostly be importing other packages into your project (versus having yours imported into others), ESM will let you not have to worry much about what kind of modules you're importing.&lt;/li&gt;
&lt;li&gt;ESM spec requires that import paths be valid paths, meaning you need the file extension and everything (CommonJS doesn't require that). Node has an option &lt;a href="https://nodejs.org/docs/latest-v16.x/api/esm.html#customizing-esm-specifier-resolution-algorithm"&gt;to skip out on that requirement&lt;/a&gt; for ESM modules, if you want to keep it old-school: &lt;code&gt;node --es-module-specifier-resolution=node your-dope-module.mjs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If you do decide to go all-in on ESM in Node, be ready to do a lot of very annoying troubleshooting!&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>node</category>
    </item>
    <item>
      <title>✂ Save time and make fewer mistakes with Snippets</title>
      <dc:creator>Adam Coster</dc:creator>
      <pubDate>Tue, 01 Jun 2021 12:32:00 +0000</pubDate>
      <link>https://dev.to/adamcoster/save-time-and-make-fewer-mistakes-with-snippets-2dl2</link>
      <guid>https://dev.to/adamcoster/save-time-and-make-fewer-mistakes-with-snippets-2dl2</guid>
      <description>&lt;p&gt;Last week I was sitting down to write a custom JavaScript error and became immediately annoyed. I've had to do that just often enough that it felt like I was being a robot, but not so often that I could do it without looking back at references.&lt;/p&gt;

&lt;p&gt;I thought, "I guess this is what &lt;a href="https://code.visualstudio.com/docs/editor/userdefinedsnippets"&gt;'snippets'&lt;/a&gt; are for, right? I should figure out how those work."&lt;/p&gt;

&lt;p&gt;After having done that... I'm now &lt;em&gt;even more annoyed&lt;/em&gt;. Because snippets are amazing, VSCode (and other editors) already have a ton of useful built-in ones, and I've been using VSCode for &lt;em&gt;years&lt;/em&gt; without even trying them out (despite &lt;em&gt;definitely&lt;/em&gt; knowing that they existed).&lt;/p&gt;

&lt;p&gt;Why hadn't I looked into them before!? Especially as a person who obsesses over automations and process improvement!&lt;/p&gt;

&lt;p&gt;In short, I misunderstood snippets. I just sorta assumed that snippets were a library of boilerplate code that you could search and then insert into your new code. &lt;em&gt;*Yawn*&lt;/em&gt;. That would mean having to search for the snippet I needed, then click around and edit the stuff that just inserted... How tedious! So I figured it was faster to write things from scratch.&lt;/p&gt;

&lt;p&gt;I had made that assumption and then didn't take the two minutes it would have taken to look them up. The first few sentences of VSCode's &lt;a href="https://code.visualstudio.com/docs/editor/userdefinedsnippets"&gt;snippet docs&lt;/a&gt; were all I would have needed to read.&lt;/p&gt;

&lt;p&gt;Whoops!&lt;/p&gt;

&lt;p&gt;It turns out that VSCode snippets have a spectacular feature that makes them so great, and that I simply didn't realize might exist: &lt;strong&gt;placeholders&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can set up a snippet to have a bunch of placeholders, and then once you've plopped the snippet into your code you can hit &lt;code&gt;tab&lt;/code&gt; to bounce from one placeholder to the next.&lt;/p&gt;

&lt;p&gt;And! If two placeholders have the same name, &lt;em&gt;they get filled out at the same time&lt;/em&gt;! The snippet can also dictate where your cursor should end up after you've filled the placeholders! It can even put in clipboard or selected text!&lt;/p&gt;

&lt;p&gt;And! You can create per-language, per-project, and user-wide libraries! So you can sync your own favorite snippets to use everywhere you go, while also making sure that your project collaborators have access to whatever snippets everyone needs to make the work easier.&lt;/p&gt;

&lt;p&gt;So much &lt;strong&gt;power&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Now I'm finally using the built-ins (they're particularly nice for &lt;code&gt;for&lt;/code&gt; loops) and I've started to build my own little libraries.&lt;/p&gt;

&lt;p&gt;Here's that dang custom error code as a Typescript snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c"&gt;/** @file MyCustom.code-snippets */&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;"Custom Error"&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;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"typescript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// The prefix is the what gets used for&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// auto-complete, so when I type "error"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// in VSCode (in a Typescript file) one&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// of the auto-complete options is this very&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// snippet.&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error-custom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"body"&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="c1"&gt;// Each entry in the array is treated as a separate&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;// line, and VSCode will use the correct newline&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;// characters based on your system and VSCode settings.&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"export class ${1:name} extends Error {"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;// The use of the tab character tells VSCode how things&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;// should be *relatively* indented, so that it can&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;// correctly auto-indent based on the code location&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;// you put the snippet into.&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;constructor(message:string) {"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="s2"&gt;super(message);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;// Here I'm referencing the first placeholder from&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;// above, so that when I'm filling it out above it&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="c1"&gt;// shows up down here as well!&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="s2"&gt;this.name = '$1';"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="s2"&gt;Error.captureStackTrace(this, this.constructor);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&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;"}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&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;"export function assert(claim:any, message = 'Assertion failed'): asserts claim {"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;if(!claim){throw new $1(message);}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&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="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;That delightful little snippet drops in a custom error class, plus an assertion to go along with it to throw that very same error. Here's an example of what it looks like fully-populated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;MyCustomError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&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="k"&gt;this&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;MyCustomError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;captureStackTrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Assertion failed&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;asserts&lt;/span&gt; &lt;span class="nx"&gt;claim&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;MyCustomError&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;And here's a Markdown snippet that converts some selected text into a link to its definition (this functionality is quite specific to a project I'm working on):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&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;"Add class to term reference"&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;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"markdown"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"term"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"body"&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;"[${TM_SELECTED_TEXT}](#term:${TM_SELECTED_TEXT/(.*)/${1:/downcase}/})"&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;p&gt;That snippet makes it so that if I select the word &lt;code&gt;Something&lt;/code&gt; and then start typing &lt;code&gt;term&lt;/code&gt;, I'll overwrite the original word and then see VSCode's auto-complete suggestions. When I choose the above snippet, VSCode &lt;em&gt;remembers&lt;/em&gt; that I had selected &lt;code&gt;Something&lt;/code&gt; before overwriting it with the snippet prefix, and so it inserts: &lt;code&gt;[Something](#term:something)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Neat!&lt;/p&gt;

&lt;p&gt;Now that I'm done kicking myself for all the wasted past efforts of &lt;em&gt;not&lt;/em&gt; using snippets, I'm extremely excited about building out my new snippet libraries and trying out those created by others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't be like me. Use snippets.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you've found any VSCode snippet libraries you really like, or have your own snippet you're particularly pumped about, please share!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was adapted from an issue of the &lt;a href="https://www.bscotch.net/post/devchat-19"&gt;DevChat&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>programming</category>
      <category>webdev</category>
      <category>vscode</category>
    </item>
    <item>
      <title>🚀 Super-charge your email with your own domain, routing, aliases, and catch-alls via Google Workspace</title>
      <dc:creator>Adam Coster</dc:creator>
      <pubDate>Sun, 16 May 2021 23:32:58 +0000</pubDate>
      <link>https://dev.to/adamcoster/super-charge-your-email-with-your-own-domain-routing-aliases-and-catch-alls-via-google-workspace-44i</link>
      <guid>https://dev.to/adamcoster/super-charge-your-email-with-your-own-domain-routing-aliases-and-catch-alls-via-google-workspace-44i</guid>
      <description>&lt;p&gt;There's a good chance you're still using an email address you set up in college, or even in high school. Or a personal email address that you wish was more professional.&lt;/p&gt;

&lt;p&gt;Changing your email address, like changing your phone number, is a &lt;em&gt;pain&lt;/em&gt;. You've probably done it as rarely as possible. Maybe you just accumulated new ones, and find yourself constantly juggling accounts.&lt;/p&gt;

&lt;p&gt;But what if you could change your email address at &lt;em&gt;any moment&lt;/em&gt; with the slightest effort? Or have different addresses for work and play? Plus another for your side hustle? Or one for each of your 10 side hustles? Even separate addresses for every single service you sign up for, to make your services harder to hack into and let you see &lt;em&gt;exactly&lt;/em&gt; who is selling your information?&lt;/p&gt;

&lt;p&gt;What about making addresses for family members, collaborators, or employees? What if you could create email accounts for &lt;em&gt;other people&lt;/em&gt; with your own domain, all addresses that you still own and control so that no important data will ever be lost no matter who comes and goes?&lt;/p&gt;

&lt;p&gt;And what if that was all centralized, and easily changeable at your whim without any stress?&lt;/p&gt;

&lt;p&gt;This article is about how you can do all of that in Gmail, through Google Workspace. Whether it's for yourself, your family, or your business, having full control over your email is a powerful thing.&lt;/p&gt;

&lt;p&gt;Unfortunately, there's no way around having one last migration (&lt;a href="https://support.google.com/a/answer/6251069" rel="noopener noreferrer"&gt;Google has documentation for that&lt;/a&gt;). But &lt;em&gt;only this one last time&lt;/em&gt;! And, also unfortunately, you can't do it for free. But given how central email is to our lives, I think you'll find the low cost worth it.&lt;/p&gt;

&lt;p&gt;💡 Tip: If you don't want to deal with migration (much data cannot even be migrated!), you could still use all the tricks here and just reroute mail to your existing inbox!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: To make this long post more readable, I've collapsed some content into expandable sections. The little triangles that toggle them open can be easy to miss!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;
  🤑 What'll it cost you?
  &lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Personal domain(s):&lt;/strong&gt; You'd probably own these anyway for your portfolio/personal site. Either way, per domain they'll cost you $8 USD to... well, the sky's the limit! It all depends on your registrar, which TLD (top-level domain, like &lt;code&gt;.com&lt;/code&gt; or &lt;code&gt;.dev&lt;/code&gt;), whether you have to buy it from a squatter, and so on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Workspace:&lt;/strong&gt; They of course have multiple tiers here, but the lowest tier ("Starter") is $6 USD/mo per user and has all the stuff you'd be getting with a free Gmail account plus a million other things. That's all I've needed to run my business!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, for my &lt;em&gt;personal&lt;/em&gt; use I have a Google Workspace subscription with one user (me) at $6 USD/mo that centralizes three domains I own (each at $12/yr through Google Domains). So it comes out to $9/mo for me to own three domains and have super-powered email.&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;
  What about non-Google alternatives?
  &lt;p&gt;Presumably the features I described above are also available in non-Gmail email services. I'm not specifically advocating that you use Google; it's what I know.&lt;/p&gt;

&lt;p&gt;This article will help you understand how all these features would work for other services, so if you don't want to use Google then use this article as something to skim for an overview of what to look for in other services.&lt;/p&gt;



&lt;/p&gt;

&lt;h2&gt;
  
  
  🔨 Getting and setting up Google Workspace
&lt;/h2&gt;

&lt;p&gt;To use Google Workspace, you're going to need a custom domain. (Or domains, plural!) Finding ones you like is definitely the hardest part of this process. The nice thing is that you can always add another domain to your Workspace and immediately start using it as if it had been your favorite domain all along (all without having to tell people to contact you at a different address or migrate all your mail), so you won't get permanently locked into a choice your past self made.&lt;/p&gt;

&lt;p&gt;I use &lt;a href="https://domains.google" rel="noopener noreferrer"&gt;Google Domains&lt;/a&gt; as my registrar. (Registrars are entities that sell web domains.) It's not the cheapest option, nor the most fully-featured, but for me the convenience of having everything under one roof is worth that. Plus I rarely need fancy features from my registrar: as long as I get free privacy and security features and easy DNS (Domain Name System) management I'm happy.&lt;/p&gt;

&lt;p&gt;If your domains are registered via Google Domains (or you transfer them to it) then adding Workspace is dead simple. Just go to your domain in the Google Domains site, and then:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Domain Overview &amp;gt; Get a custom email with Workspace&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And follow the prompts. Google will even set up your DNS records for you. Magic!&lt;/p&gt;

&lt;p&gt;If you're using a non-Google registrar then... uh... hopefully the main &lt;a href="https://workspace.google.com/" rel="noopener noreferrer"&gt;Google Workspace site&lt;/a&gt; has good onboarding and your registrar has a nice UI.&lt;/p&gt;

&lt;p&gt;I'm sure you'll be fine...&lt;/p&gt;

&lt;h2&gt;
  
  
  🔑 Log into your new Workspace Gmail account
&lt;/h2&gt;

&lt;p&gt;Part of the Google Workspace setup included you creating your first account, which also results in an email address.&lt;/p&gt;

&lt;p&gt;Let's say your domain is &lt;code&gt;my-amazing-domain.tld&lt;/code&gt; and your name is... uh... Flux. So your email address would be &lt;code&gt;flux@my-amazing-domain.tld&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Anywhere you would see a Google login page (including websites that use Google sign-in) you can plug in your new email address and password, just like you would with a plain Gmail account.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧭 Finding and navigating the Admin console
&lt;/h2&gt;

&lt;p&gt;
  💡 Tip: Don't use the Admin mobile app
  &lt;p&gt;It does have a mobile app. Technically. But the mobile app has super limited features and the general consensus seems to be: don't use it.&lt;/p&gt;

&lt;p&gt;Use a non-mobile browser for all the stuff we talk about here.&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;When you create a Workspace account, you get access to a new "Admin" console for managing it. You can get to the Admin console in your browser, either directly via &lt;a href="https://admin.google.com" rel="noopener noreferrer"&gt;admin.google.com&lt;/a&gt; or via your Gmail inbox using that incomprehensible 3x3 dot-grid icon in the top right:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FsJCMPSI.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FsJCMPSI.jpg" alt="Screenshot showing the grid icon in Gmail that displays all Google apps, including the Admin app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Admin portal is where most of the power of Workspace comes in. I encourage you to poke around and explore all the things you can do.&lt;/p&gt;

&lt;p&gt;The UI has some... I guess we'll say, "design issues." If you get confused or stuck, trust me when I say it's not your fault. If you think there should be an option somewhere but can't find it, try hovering over or clicking things. There are secrets everywhere.&lt;/p&gt;

&lt;p&gt;
  Example of the console's confusing UI
  &lt;p&gt;At &lt;a href="https://admin.google.com/ac/appslist/core" rel="noopener noreferrer"&gt;&lt;code&gt;Admin &amp;gt; Apps &amp;gt; Google Workspace&lt;/code&gt;&lt;/a&gt; you may see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FU4nMLVz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FU4nMLVz.png" alt="Screenshot of the Workspace Services listing in Google Workspace admin console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you get there you'll probably try to click the checkbox to select Gmail, only to find that this just lets you turn services on or off. Then you might notice that if you hover over the Gmail row one of those three-vertical-dot icons appears on the right! But all that lets you do is turn the service on/off, too. So then you'll think you must be in the wrong place... You're not! Turns out that the whole Gmail table-row is a button/link that takes you to the Gmail Admin settings.&lt;/p&gt;



&lt;/p&gt;

&lt;h2&gt;
  
  
  👤 Aside: User Profiles
&lt;/h2&gt;

&lt;p&gt;While this article is about the Gmail Admin part of Google Workspace, there's a related point of frustration you'll immediately hit: if you try to add a picture to your profile, you'll see that you &lt;em&gt;don't have permission to do that&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That's a super weird default, especially for the &lt;em&gt;admin user&lt;/em&gt; (you).&lt;/p&gt;

&lt;p&gt;To fix it, go to &lt;a href="https://admin.google.com/ac/directory/profileediting" rel="noopener noreferrer"&gt;&lt;code&gt;Admin &amp;gt; Directory &amp;gt; Directory Settings &amp;gt; Profile Editing&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  👥📧 Users, "recognized", and "unrecognized" addresses
&lt;/h2&gt;

&lt;p&gt;You're probably used to thinking about email addresses as being synonymous with a user account. An email address is something that goes to an email inbox, right?&lt;/p&gt;

&lt;p&gt;While an email address &lt;em&gt;can&lt;/em&gt; point to an inbox, it doesn't have to. An email address is just like a URL: &lt;em&gt;the server that controls the address/URL can decide what it means&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Any time you send an email, a server (i.e. "someone else's computer") somewhere catches that request and decides what to do with it. And it could do anything! Re-route it, modify it, block it, auto-send something back to the sender, add logs, evaluate spamminess, flag the sender as a spammer, convert the email into Pig Latin, send a copy to an archive, parse it with a robot to do cool weird stuff that has nothing to do with email, and on and on.&lt;/p&gt;

&lt;p&gt;Literally anything!&lt;/p&gt;

&lt;p&gt;So let's clarify and create some terms so you don't get lost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User:&lt;/strong&gt; An account &lt;em&gt;within&lt;/em&gt; your Workspace, identified by a unique email address &lt;em&gt;as a username&lt;/em&gt;, that has its own Gmail inbox, Google Docs, and so on. It's probably assigned to a single person, though it doesn't have to be. It's the same concept as any other Google account, just managed by your Workspace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Envelope:&lt;/strong&gt; A term used by email services that is basically synonymous with "email address".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Address:&lt;/strong&gt; A unique email address for one of the domains attached to your Workspace. E.g. &lt;code&gt;me@my-rad-domain.tld&lt;/code&gt;. While a User has an Address, an Address &lt;em&gt;doesn't have to belong to any user&lt;/em&gt;. A user could have multiple addresses that all point to the same inbox. Addresses can be used for lots of things, including identifying a User, identifying a Group, and setting up routing rules.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recognized Address:&lt;/strong&gt; An address that is assigned to a known resource within your workspace (e.g. a User, Group, or other kind of entity).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unrecognized Address:&lt;/strong&gt; An address that doesn't point to any known resource.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;💡 Tip: While you may end up with many aliases, you only have &lt;em&gt;one username&lt;/em&gt;. Every time you sign into your account, or use Google Sign-in somewhere else, you'll need to use your account's &lt;em&gt;primary&lt;/em&gt; address as the username.&lt;/p&gt;

&lt;h2&gt;
  
  
  ♾ Create infinite addresses and aliases
&lt;/h2&gt;

&lt;p&gt;There are three main approaches to bending email addresses to your will in Workspace:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use "mapping" to set up one-to-one mappings of one address to another: &lt;a href="https://admin.google.com/AdminHome?hl=en#ServiceSettings/service=email&amp;amp;subtab=filters" rel="noopener noreferrer"&gt;&lt;code&gt;Admin &amp;gt; Apps &amp;gt; Google Workspace &amp;gt; Gmail &amp;gt; Advanced Settings&lt;/code&gt;&lt;/a&gt; &lt;code&gt;&amp;gt; Recipient address map&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use "routing" rules to set up more complex mappings: &lt;a href="https://admin.google.com/AdminHome?hl=en#ServiceSettings/service=email&amp;amp;subtab=filters" rel="noopener noreferrer"&gt;&lt;code&gt;Admin &amp;gt; Apps &amp;gt; Google Workspace &amp;gt; Gmail &amp;gt; Advanced Settings&lt;/code&gt;&lt;/a&gt; &lt;code&gt;&amp;gt; Routing&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use "aliased" domains so that &lt;code&gt;me@professional-af.tld&lt;/code&gt; and &lt;code&gt;me@everything-is-a-joke.tld&lt;/code&gt; all work synonymously with &lt;code&gt;me@main-domain.tld&lt;/code&gt;: &lt;a href="https://admin.google.com/ac/domains/manage" rel="noopener noreferrer"&gt;&lt;code&gt;Admin &amp;gt; Account &amp;gt; Domains &amp;gt; Manage Domains&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In all cases, the re-routed addresses basically work as aliases. Mail sent to them gets routed right into the inbox (or other target) you want them to go into.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚠ Important! ⚠
&lt;/h3&gt;

&lt;p&gt;Before you do any custom routing, there are two things you need to know:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You could accidentally create routing that prevents you from getting emails you expect to get. Do experiments by sending emails from &lt;em&gt;other&lt;/em&gt; accounts to see if they appear where they're supposed to. If you mess something up, don't worry. As long as you can log into your Workspace Admin console you can fix it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You must disable the default "Default Routing" entry&lt;/strong&gt; or else nothing else will work!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To disable the default "Default Routing" setting, go to &lt;a href="https://admin.google.com/ac/apps/gmail/defaultrouting?hl=en" rel="noopener noreferrer"&gt;&lt;code&gt;Admin &amp;gt; Apps &amp;gt; Google Workspace &amp;gt; Gmail &amp;gt; Default Routing&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You'll see an entry in a table, with a button that says "Disable". Disable it! You can always turn it back on later (unless you delete it).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2Fx6LsoLL.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2Fx6LsoLL.png" alt="Screenshot showing the default entry of the Default Routing table in Google Workspace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can't figure out what that default entry is doing. It wasn't there the first time I set up G Suite, years ago. All I know is that it prevented any other routing rules I added from working.&lt;/p&gt;

&lt;h3&gt;
  
  
  🗺 Recipient Address Mapping
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://admin.google.com/AdminHome?hl=en#ServiceSettings/service=email&amp;amp;subtab=filters" rel="noopener noreferrer"&gt;&lt;code&gt;Admin &amp;gt; Apps &amp;gt; Google Workspace &amp;gt; Gmail &amp;gt; Advanced Settings&lt;/code&gt;&lt;/a&gt; &lt;code&gt;&amp;gt; Recipient address map&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now for the simplest option: 1-to-1 mapping. This approach is particularly useful if you want to take an &lt;em&gt;allowlist&lt;/em&gt; approach to addresses at your domain, so that people have to know one of your explicitly-defined addresses to be able to send you anything. This is great for reducing unsolicited mail.&lt;/p&gt;

&lt;p&gt;
  Sample use cases
  &lt;ul&gt;
&lt;li&gt;Map &lt;code&gt;sales@mydomain&lt;/code&gt; to &lt;code&gt;your-name@mydomain&lt;/code&gt; to create a role-specific address that can be changed to point to a different user at any time.&lt;/li&gt;
&lt;li&gt;Map &lt;code&gt;very-important-stuff@mydomain&lt;/code&gt; to both your and an assistant's addresses.&lt;/li&gt;
&lt;li&gt;Map &lt;code&gt;for-robots@mydomain&lt;/code&gt; to a 3rd party email-based service that will do cool stuff with emails that get sent to it.&lt;/li&gt;
&lt;li&gt;Map &lt;code&gt;past-employee@mydomain&lt;/code&gt; to &lt;code&gt;new-employee@mydomain&lt;/code&gt; for seamless employee onboarding.&lt;/li&gt;
&lt;li&gt;Map &lt;code&gt;old-address@secondary-domain.tld&lt;/code&gt; to &lt;code&gt;new-address@primary-domain.tld&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Map &lt;code&gt;temporary-address@mydomain&lt;/code&gt; to &lt;code&gt;permanent@mydomain&lt;/code&gt;, so that you can sign up for things that might end up selling your address and then later re-route messages coming into &lt;code&gt;temporary-address&lt;/code&gt; right into spam (or cause them to bounce).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And so on and so forth. If you have any creative uses for such aliases, please share in the comments!&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;Google has &lt;a href="https://support.google.com/a/answer/4524505" rel="noopener noreferrer"&gt;pretty clear instructions&lt;/a&gt; for setting these up, and the interface isn't too complicated.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚦 Routing
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://admin.google.com/AdminHome?hl=en#ServiceSettings/service=email&amp;amp;subtab=filters" rel="noopener noreferrer"&gt;&lt;code&gt;Admin &amp;gt; Apps &amp;gt; Google Workspace &amp;gt; Gmail &amp;gt; Advanced Settings&lt;/code&gt;&lt;/a&gt; &lt;code&gt;&amp;gt; Routing&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Workspace's "Recipient Address Mapping" rules (described above) are just stripped down and simplified routing rules. You can do all that stuff and more with the fancy, full-featured Routing options.&lt;/p&gt;

&lt;p&gt;
  Sample use cases
  &lt;ul&gt;
&lt;li&gt;Do all the stuff listed in the prior section.&lt;/li&gt;
&lt;li&gt;Create a catch-all address, so that any Unrecognized Address will route to you. This is great if you want to use a different address for every service you sign up for, for example.&lt;/li&gt;
&lt;li&gt;Route mail from &lt;em&gt;specific&lt;/em&gt; senders (or groups of senders) to a different address. Maybe you want most of your mail to go to an assistant or to an automated service, but certain mail to &lt;em&gt;always&lt;/em&gt; go right into your inbox.&lt;/li&gt;
&lt;li&gt;For a service that requires a &lt;em&gt;public&lt;/em&gt; email address, create a route for that address that only allows emails &lt;em&gt;from that service&lt;/em&gt; through. That way you can use the service without getting spammed! (Looking at you, npm...)&lt;/li&gt;
&lt;li&gt;Use a regex pattern to apply rules to a &lt;em&gt;collection&lt;/em&gt; of addresses. This can provide a nice middle ground between the 1-to-1 mapping and catch-all approaches.&lt;/li&gt;
&lt;li&gt;Add a prefix to messages from certain senders.&lt;/li&gt;
&lt;li&gt;Create spam traps that dump messages right into spam before they even hit your inbox.&lt;/li&gt;
&lt;li&gt;Prevent a range of addresses from ever going to spam.&lt;/li&gt;
&lt;li&gt;Add custom headers, for auditing purposes or for use by robots.&lt;/li&gt;
&lt;li&gt;Re-write the recipient address while keeping the domain or username parts constant.&lt;/li&gt;
&lt;li&gt;Strip attachments from certain emails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And more!&lt;/p&gt;

&lt;p&gt;(While I've mostly been talking about routing email sent &lt;em&gt;to&lt;/em&gt; your domain, you can do all that same stuff with email sent &lt;em&gt;from&lt;/em&gt; your domain!)&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;&lt;a href="https://support.google.com/a/answer/6297084" rel="noopener noreferrer"&gt;Google's docs for routing&lt;/a&gt; are accurate and in-depth, though it gets a bit complicated. I encourage you to read through the details for all the options there, but the key concepts are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inbound vs. Outbound: You can apply a routing rule to messages coming in, going out, or both!&lt;/li&gt;
&lt;li&gt;Envelope filter: "envelope" just means "address". You can filter (limit) which addresses are affected by a rule.&lt;/li&gt;
&lt;li&gt;Envelope recipient: The main way you re-route email is by modifying the recipient!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
  Sample options for a catch-all address
  &lt;ul&gt;
&lt;li&gt;
&lt;code&gt;✅ Inbound&lt;/code&gt; (applies to email being sent to you)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Headers &amp;gt; ✅ Add X-Gm-Original-To&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Subject &amp;gt; Prepend Custom Subject&lt;/code&gt;, with something like "[rerouted]". I use this so that I can tell which emails in my inbox came from the catch-all route.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Envelope Recipient &amp;gt; ✅ Change envelope recipient&lt;/code&gt;. This is how you make it so that the fake address gets changed to a real one. You've got some options. You'll probably want to set it to &lt;code&gt;recipient-address@existing-domain&lt;/code&gt; or fully custom &lt;code&gt;recipient-address@some-other-domain&lt;/code&gt;. Whichever address goes to the &lt;em&gt;actual inbox&lt;/em&gt; you want to receive this stuff.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Show Options &amp;gt; Account Types &amp;gt; ✅ Unrecognized/Catch-all&lt;/code&gt; (this is at the very bottom of the form) to only apply this rule to Unrecognized Addresses.&lt;/li&gt;
&lt;/ul&gt;



&lt;/p&gt;

&lt;h3&gt;
  
  
  🔗 Aliased Domains
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://admin.google.com/ac/domains/manage" rel="noopener noreferrer"&gt;&lt;code&gt;Admin &amp;gt; Account &amp;gt; Domains &amp;gt; Manage Domains&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, the simplest kind of routing in Workspace is to alias an &lt;em&gt;entire domain&lt;/em&gt; to your primary domain, causing all emails sent to any of those domains to route to the primary one. So &lt;code&gt;this-username@onedomain.tld&lt;/code&gt; will get routed as if it's &lt;code&gt;this-username@someotherdomain.tld&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This feature is what allows you to centralize any number of domains under one Workspace, giving you tons of flexibility and address future-proofing. If you buy a new domain tomorrow and want to start using it to replace your old address, you just can! And you can route all messages sent to it right to your existing inbox. Your old addresses will keep on working, so you don't have to worry about lost connections.&lt;/p&gt;

&lt;p&gt;Plus, you can do all that same routing stuff we talked about earlier, but can apply different rules per domain!&lt;/p&gt;

&lt;p&gt;A big functional difference compared to using mapping/routing is that the addressee ("envelope recipient") is not rewritten. If you look at the "to" field for incoming, aliased addresses, you'll see that the originally-targeted domain is still there. Even if your inbox uses a different domain!&lt;/p&gt;

&lt;p&gt;This is a huge benefit over the other forms of routing, since the others lose information about the original intended recipient address (you can use the &lt;code&gt;Add Subject Prefix&lt;/code&gt; and/or &lt;code&gt;Add X-Gm-Original-To header&lt;/code&gt; options to mitigate that).&lt;/p&gt;

&lt;p&gt;Note that Workspace gives you &lt;a href="https://support.google.com/a/answer/7502379" rel="noopener noreferrer"&gt;two ways to centralize domains&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;As Aliases (as described in this section).&lt;/li&gt;
&lt;li&gt;As Secondary Domains, which do &lt;em&gt;not&lt;/em&gt; work out of the box as aliases. You can still use custom routes to map addresses from one domain onto another, but they won't be true aliases (the envelope recipient will get rewritten). This option is intended for use cases where you have different sets of &lt;em&gt;users&lt;/em&gt; for each domain.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To &lt;a href="https://admin.google.com/ac/domains/manage" rel="noopener noreferrer"&gt;add your other domains&lt;/a&gt; you'll have to first prove ownership with a DNS Text record, and then allow email routing with a DNS MX record. This is also very easy if you're using Google Domains. In any case, you'll see help links and prompts that should get you all squared away.&lt;/p&gt;

&lt;p&gt;💡 Tip: The "Manage Domains" Workspace UI is another confusing one: try hovering over the rows to have the things you can do pop up as buttons.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎉 Other fun things you can do with Google Workspace
&lt;/h2&gt;

&lt;p&gt;This article is all about Gmail, but I'd be remiss if I didn't point out some other cool things you can do, ever so briefly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replace the Google logo with a custom one: &lt;a href="https://admin.google.com/ac/companyprofile/personalization" rel="noopener noreferrer"&gt;&lt;code&gt;Admin &amp;gt; Account &amp;gt; Account Settings &amp;gt; Personalization&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Get the newest Workspace features quickly: &lt;a href="https://admin.google.com/ac/accountsettings/preferences" rel="noopener noreferrer"&gt;&lt;code&gt;Admin &amp;gt; Account &amp;gt; Account Settings &amp;gt; Preferences &amp;gt; New Features &amp;gt; Rapid Release&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Customize Google Apps URLs: &lt;a href="https://admin.google.com/ac/accountsettings/customurl" rel="noopener noreferrer"&gt;&lt;code&gt;Admin &amp;gt; Account &amp;gt; Account Settings &amp;gt; Custom URLs&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Please share!
&lt;/h2&gt;

&lt;p&gt;Are you doing anything cool with email routing? Do you have any hard-won lessons? If you're using a service besides Google Workspace, how do you like it? What about pro tips for migrating data?&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>tutorial</category>
      <category>startup</category>
      <category>google</category>
    </item>
    <item>
      <title>Live Dev: How to set up a Node project with Typescript</title>
      <dc:creator>Adam Coster</dc:creator>
      <pubDate>Fri, 14 May 2021 20:01:24 +0000</pubDate>
      <link>https://dev.to/adamcoster/how-to-set-up-a-node-project-with-typescript-d5c</link>
      <guid>https://dev.to/adamcoster/how-to-set-up-a-node-project-with-typescript-d5c</guid>
      <description>&lt;p&gt;Learning how to code is a long process. Structured coursework and tutorials are a great way for most people to learn, especially at the beginning, but at some point you start to wonder... what does the &lt;em&gt;actual work&lt;/em&gt; of development look like? How do you go from identifying a problem to a deployable project that &lt;em&gt;solves&lt;/em&gt; that problem?&lt;/p&gt;

&lt;p&gt;I had a (relatively) small side project I wanted to tackle for fun, and decided to do it live on video so that people could see the whole process from start to finish.&lt;/p&gt;

&lt;p&gt;If you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Love the Node/JavaScript/Typescript ecosystem and want to get &lt;em&gt;real deep&lt;/em&gt; into how it all works together; or&lt;/li&gt;
&lt;li&gt;Are having trouble using your programming knowledge to build full solutions; or&lt;/li&gt;
&lt;li&gt;Are actively developing your fluency in programming in general or JavaScript/Node/Typescript in specific; or&lt;/li&gt;
&lt;li&gt;Enjoy the calming background sounds of someone talking about nerd stuff&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;... then this will be right up your alley!&lt;/p&gt;

&lt;p&gt;In Episode 1 I go through the process of setting up a new Node + Typescript project and present the problem being solved: converting Git logs into changelogs/patchnotes.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/OMCAAcfWLD4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;If you want to joint the next one live to ask questions or tell me when I'm wrong, &lt;a href="https://twitch.tv/bscotch"&gt;subscribe on Twitch&lt;/a&gt; and/or &lt;a href="https://twitter.com/costerad"&gt;follow me on Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How Notion's URLs change without breaking your links</title>
      <dc:creator>Adam Coster</dc:creator>
      <pubDate>Wed, 05 May 2021 13:39:42 +0000</pubDate>
      <link>https://dev.to/adamcoster/change-a-url-without-breaking-existing-links-4m0d</link>
      <guid>https://dev.to/adamcoster/change-a-url-without-breaking-existing-links-4m0d</guid>
      <description>&lt;p&gt;I noticed something a while ago while updating the title of a page on one of my favorite webapps (&lt;a href="https://www.notion.so/"&gt;Notion&lt;/a&gt;): &lt;em&gt;the URL changed to reflect the new title&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;My first thought was, "Well that's dumb, now my links to this page will be broken!" But then my links &lt;em&gt;weren't&lt;/em&gt; broken, so I did some experiments with Notion URLs to figure out what kind of sorcery they were doing. Turns out it was simple!&lt;/p&gt;

&lt;p&gt;For some background: a URL is supposed to be a unique, &lt;em&gt;unchanging&lt;/em&gt; identifier for something on the Internet. When you write, say, a blog post, you'll typically end up with a URL that uses a weird "URL-friendly" version of that title (e.g. "What I ate this morning!" becomes "example.com/post/what-i-ate-this-morning"). Once you've published, most blogging software locks your URL. Why? Because if you change it you'll &lt;em&gt;break any links to that page&lt;/em&gt;. That's terrible for people on the Internet clicking those links and terrible for your SEO.&lt;/p&gt;

&lt;p&gt;This leads to the annoying scenario where the title shown in the URL deviates from that in the actual post, because that first title was a draft title that got locked in.&lt;/p&gt;

&lt;p&gt;That's not too big of a deal, since blog posts and other old-school web pages don't change that much. Even when the title does change, the content is probably largely the same. So the old title (and thus URL) are still accurate enough.&lt;/p&gt;

&lt;p&gt;But then you have something like Notion, where content is extremely dynamic by design. In this case, the title of a page could change all the time, and the content may eventually deviate completely from the initial version.&lt;/p&gt;

&lt;p&gt;Notion has done something clever here. They have the URL &lt;em&gt;always reflect the title&lt;/em&gt;. If you're a Notion user you can see it happen. &lt;strong&gt;Live!&lt;/strong&gt; Just start editing the title and watch your URL...&lt;/p&gt;

&lt;p&gt;But if they change the URL when you change the title, doesn't that break all the links to that page?&lt;/p&gt;

&lt;p&gt;Nope! Each URL ends with a unique identifier (e.g. &lt;code&gt;notion.so/me/What-I-Ate-This-Morning-2345234523452345&lt;/code&gt;). That unique suffix stays constant while the other part changes.&lt;/p&gt;

&lt;p&gt;What Notion is doing here is &lt;em&gt;ignoring&lt;/em&gt; the part before the identifier when a request hits their server. It uses the never-changing identifier to find the page in question, then gets the &lt;em&gt;current&lt;/em&gt; name of that page and computes what the URL &lt;em&gt;should&lt;/em&gt; be with the current title of that page. If that's different from where you started, it redirects you (the redirect doesn't require re-rendering, since Notion is a Single Page App, so you'd only notice this if you watch the URL bar in your browser).&lt;/p&gt;

&lt;p&gt;You can see this in action yourself on Notion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a page&lt;/li&gt;
&lt;li&gt;Set the title&lt;/li&gt;
&lt;li&gt;Grab the URL (which should have your current title in it)&lt;/li&gt;
&lt;li&gt;Delete the title part of the URL (keeping the identifier)&lt;/li&gt;
&lt;li&gt;Paste that into a new browser tab and hit ENTER.&lt;/li&gt;
&lt;li&gt;Watch the page and the browser's URL bar&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In fact, you can put &lt;em&gt;any&lt;/em&gt; title in front of that identifier, whether it was ever a title of your page or not, and you'll still end up in the right place.&lt;/p&gt;

&lt;h2&gt;
  
  
  But... SEO!
&lt;/h2&gt;

&lt;p&gt;While being able to change the link at any time without breaking existing links is super neat, the immediate next question should be what happens to SEO for that page. Each distinct link will be treated as a &lt;em&gt;different page&lt;/em&gt; by search engines, so every time the URL changes for that page you'll be diluting your SEO juice.&lt;/p&gt;

&lt;p&gt;Fortunately, there's a mechanism you can use to keep those SEO juices flowing: &lt;strong&gt;set the canonical URL to the unchanging version of that URL&lt;/strong&gt; (the one with just the ID).&lt;/p&gt;

&lt;p&gt;The "Canonical URL" is a piece of metadata you can add to a page that tells the browser (and search engines), "Hey, no matter how you got to this page, the following URL is the &lt;em&gt;real&lt;/em&gt; URL for this content."&lt;/p&gt;

&lt;p&gt;You can stick this right into the head of an HTML document with a link element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"canonical"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/my-full-unchanging-url"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, the server can add it to the &lt;code&gt;Link&lt;/code&gt; HTTP response header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Link: &amp;lt;https://example.com/my-full-unchanging-url&amp;gt;; rel="canonical"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, Notion does &lt;em&gt;not&lt;/em&gt; use canonical URL metadata on its pages, so this neat feature probably has a bad impact on SEO. People probably don't host a lot of public content that needs good SEO, so maybe that's no big deal.&lt;/p&gt;

&lt;p&gt;Notion has made one mistake here, though: they don't set the Canonical URL in either the HTML head (via the &lt;code&gt;&amp;lt;link rel='canonical'/&amp;gt;&lt;/code&gt; tag) or the HTTP response headers (via the &lt;code&gt;Link&lt;/code&gt; header). They should have done this using the ID-only (no title) URL. Without that, search engines will think they're seeing a different page every time they follow a different URL to get there (blowing up your SEO).&lt;/p&gt;

&lt;p&gt;For sites with less content (personal blogs and the like) you could have very short identifiers, allowing you to use this trick without hideous URLs!&lt;/p&gt;

&lt;p&gt;I'll probably be using this in my own projects, can you think of any use cases for it?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>programming</category>
      <category>html</category>
    </item>
    <item>
      <title>😴 Laziness isn't real</title>
      <dc:creator>Adam Coster</dc:creator>
      <pubDate>Mon, 26 Apr 2021 13:02:58 +0000</pubDate>
      <link>https://dev.to/adamcoster/laziness-isn-t-real-1oel</link>
      <guid>https://dev.to/adamcoster/laziness-isn-t-real-1oel</guid>
      <description>&lt;p&gt;For most of my life, I was held up as a model of Puritanical work ethic through all jillion years of my schooling (K-PhD). I was the shining example of not being "lazy".&lt;/p&gt;

&lt;p&gt;Right?&lt;/p&gt;

&lt;p&gt;The reality was that I had enough privilege and luck to be able to design my life such that my own particular brand of "laziness" had a minimum negative impact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I had ADHD (but didn't know it), and the &lt;em&gt;hyperfocus&lt;/em&gt; aspect made it super easy to do things that I found interesting.&lt;/li&gt;
&lt;li&gt;I found ways to structure my life and goals so that I could neglect the stuff I wasn't interested in.&lt;/li&gt;
&lt;li&gt;My interests aligned with those that American society deems Important and Industrious (math, science, etc). If I wasn't interested in these things I probably wouldn't have been able to focus on them.&lt;/li&gt;
&lt;li&gt;My forgetfulness and social detachment were easily classified by everyone else as fitting the "Absent-Minded Professor" archetype. Consequently, I was granted permission out of the gate for many things that would be seen as "laziness" in others.&lt;/li&gt;
&lt;li&gt;I didn't have to surmount constant, systemic oppression. People believed in me &lt;em&gt;by default&lt;/em&gt;, and I didn't have to expend enormous energy just getting to the table. I was already at the table, with all my energy, ready to work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This list could keep going and going, but that's probably enough to make the point that "industriousness" -- and therefore its opposite, "laziness" -- are &lt;em&gt;context-dependent&lt;/em&gt; properties that societally we treat as &lt;em&gt;context-independent&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I've been thinking about this a lot recently, mostly triggered by learning that I had ADHD while also realizing I had developed a mild depression over the past couple of years.&lt;/p&gt;

&lt;p&gt;The depression was raising all of my energy thresholds. The thing about depression is that it makes you disinterested in things. The thing about hyperfocus is that it requires your interest. So depression gave me all the downsides of ADHD without the upside.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Note that hyperfocus is only an upside in the right context. It is often a huge downside because people with ADHD can't choose what they hyperfocus on.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For the first time in my life, I was having trouble doing things that &lt;em&gt;I wanted to do&lt;/em&gt;. This lead to the question... was I being &lt;em&gt;lazy&lt;/em&gt;? I certainly thought that I was, which didn't help matters any since that added guilt on top of all the rest. It wasn't until realizing I was depressed that I stepped back to think about the &lt;em&gt;context&lt;/em&gt; of laziness.&lt;/p&gt;

&lt;p&gt;Fortunately, there are more knowledgeable people than me who have already been thinking about this for years and years. Go read &lt;a href="https://humanparts.medium.com/laziness-does-not-exist-3af27e312d01"&gt;"Laziness Does Not Exist"&lt;/a&gt; by Devon Price (which was recently turned into a book on the topic). I hadn't seen this until a few weeks ago, which reminded me that I wanted to do some writing on the topic.&lt;/p&gt;

&lt;p&gt;Devon focuses that article on how mental health and privilege intersect with societal ideas of academic laziness. This is a super important aspect of the problem, which I defer to Devon and others and recommend that you go explore.&lt;/p&gt;

&lt;p&gt;What I want to do here is take a step back to ask what we &lt;em&gt;even mean&lt;/em&gt; when we think of ourselves or others as being "lazy".&lt;/p&gt;

&lt;p&gt;My bet is that if you try to define it you'll end up with something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Laziness: Avoiding doing something you're supposed to be doing, due to a character flaw.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We could spend hours fine-tuning that definition, but I think you'll have a hard time finding one that others would find accurate that also means something substantively different.&lt;/p&gt;

&lt;p&gt;We define laziness relative to "supposed to", and use it as a moralistic judgment about a person's fundamental being.&lt;/p&gt;

&lt;p&gt;This whole "supposed to" thing so often goes unexplored. &lt;em&gt;Why&lt;/em&gt; are you supposed to be doing these things in the first place? Why are you supposed to do them &lt;em&gt;in that way&lt;/em&gt;? Why &lt;em&gt;aren't&lt;/em&gt; you supposed to be doing these other things?&lt;/p&gt;

&lt;p&gt;Who gets to decide?&lt;/p&gt;

&lt;p&gt;I think that the message of the "Laziness Doesn't Exist" article is exactly right, which is that the moralistic idea of laziness is toxic, anti-human nonsense.&lt;/p&gt;

&lt;p&gt;When the word "lazy" comes up, treat it as the discovery of a &lt;em&gt;structural problem&lt;/em&gt;. By identifying someone (including yourself) as lazy, what you've actually done is uncovered a conflict between the context of &lt;em&gt;reality&lt;/em&gt; (how things &lt;em&gt;are&lt;/em&gt;) and the context of your &lt;em&gt;imagination&lt;/em&gt; (how things "&lt;em&gt;should be&lt;/em&gt;"). That means it's time to dig deep and find out what's really going on.&lt;/p&gt;

&lt;p&gt;If you find yourself thinking that you are being lazy, or that someone else is, here are some questions to help you guide you to a more humanistic and empathic interpretation and hopefully reveal your next steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Why this task?&lt;/li&gt;
&lt;li&gt;Why this person?&lt;/li&gt;
&lt;li&gt;Why might this task be particularly hard for this person?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example time!&lt;/p&gt;

&lt;p&gt;In college I spent most of my hours glued to science textbooks and doing related coursework and lab work. I never turned something in late, and usually had it done well in advance.&lt;/p&gt;

&lt;p&gt;For my non-science courses, on the other hand... I put in the least effort possible and delayed all tasks until the last possible moment. I was, in a word, "lazy." But... why?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Why this task?&lt;/strong&gt; I took those courses because they were required to meet graduation requirements. I would have rather been in the lab, in another science class, or really anywhere else. Someone else decided this was important but didn't convince me of that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why this person?&lt;/strong&gt; My school required that &lt;em&gt;everyone&lt;/em&gt; take courses across a variety of disciplines. I had to because everyone did. It had nothing to do with my interests or goals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why is it so hard?&lt;/strong&gt; Turns out I had undiagnosed ADHD. The hyperfocus aspect allowed me to excel at the stuff I was interested in. The other aspects made it hard to do anything else. It has always been important to me to know &lt;em&gt;why&lt;/em&gt;, and "because!" has always been an answer that chaffed. Probably because the &lt;em&gt;why&lt;/em&gt; is what allowed me to become interested, which in turn allowed me to excel. For a person like me, something I don't see the importance of is something I can't pay attention to.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Importantly, the fact that laziness is the outcome of a structural problem &lt;em&gt;doesn't necessarily let you off the hook&lt;/em&gt; for not doing something. But taking out the nonsense moralistic component to get to the &lt;em&gt;why&lt;/em&gt; allows uncovering the responsible structural issues so that they can be addressed where possible, and without blame. Where you can't address them directly, explicit awareness still lets you work on management and coping strategies.&lt;/p&gt;

&lt;p&gt;The next time you start getting down on yourself about being "lazy", remember that &lt;em&gt;there's no such thing&lt;/em&gt; and then dig deep.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A version of this article originally appeared in the &lt;a href="https://www.bscotch.net/post/devchat-13"&gt;DevChat Newsletter&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>discuss</category>
      <category>motivation</category>
      <category>mentalhealth</category>
    </item>
  </channel>
</rss>
