<?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: Alexey</title>
    <description>The latest articles on DEV Community by Alexey (@alexeydc).</description>
    <link>https://dev.to/alexeydc</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%2F593449%2Fb13ee18a-0d68-41ba-aff3-cd034a3c4453.jpeg</url>
      <title>DEV Community: Alexey</title>
      <link>https://dev.to/alexeydc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexeydc"/>
    <language>en</language>
    <item>
      <title>WebGPU tutorial: compute, vertex, and fragment shaders on the web</title>
      <dc:creator>Alexey</dc:creator>
      <pubDate>Thu, 16 Jan 2025 21:27:43 +0000</pubDate>
      <link>https://dev.to/alexeydc/webgpu-tutorial-compute-vertex-and-fragment-shaders-on-the-web-10db</link>
      <guid>https://dev.to/alexeydc/webgpu-tutorial-compute-vertex-and-fragment-shaders-on-the-web-10db</guid>
      <description>&lt;p&gt;A gift to the world, WebGPU promises to bring cutting edge GPU compute capabilities to the web - to all consumer platforms everywhere with a shared codebase.&lt;/p&gt;

&lt;p&gt;Its predecessor, WebGL, fantastic in its own right, sorely lacked compute shader functionality - limiting applications.&lt;/p&gt;

&lt;p&gt;WGSL - the WebGPU shader/compute language - borrows from the best in its domain: Rust and GLSL.&lt;/p&gt;

&lt;p&gt;This tutorial fills a gap in documentation I felt when I was learning to work with WebGPU: I wanted a simple starting point for using a compute shader to calculate data for the vertex and fragment shaders.&lt;/p&gt;

&lt;p&gt;The single-file HTML with all the code explained in this tutorial can be found at &lt;a href="https://github.com/alexey-dc/webgpu_html/blob/main/000_compute_vertex_fragment.html" rel="noopener noreferrer"&gt;https://github.com/alexey-dc/webgpu_html/blob/main/000_compute_vertex_fragment.html&lt;/a&gt; - read on for the breakdown.&lt;/p&gt;

&lt;p&gt;Here's that HTML running on my domain for a single click demo: &lt;a href="https://alexey-dc.com/wgsl_demo" rel="noopener noreferrer"&gt;https://alexey-dc.com/wgsl_demo&lt;/a&gt; (requires WebGPU-enabled browser like Chrome or Edge &lt;a href="https://caniuse.com/webgpu" rel="noopener noreferrer"&gt;https://caniuse.com/webgpu&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  High level setup
&lt;/h2&gt;

&lt;p&gt;This is a particle simulation - that is it occurs over time, in timesteps.&lt;/p&gt;

&lt;p&gt;Time is tracked in JS/on the CPU, passed in as a (float) uniform to the GPU.&lt;/p&gt;

&lt;p&gt;The particle data is managed entirely on the GPU - though there is still a handshake with the CPU that allows allocating the memory and setting initial values. It's also possible to read the data back to the CPU, but that's left out of this tutorial.&lt;/p&gt;

&lt;p&gt;The magic of this setup is that every particle is updated in parallel with all other particles, enabling once mind-boggling compute and render speed capabilities in the browser (the parallelization maxes out at the number of cores on the GPU; we can divide the number of particles by the number of cores to get the true number of cycles per core per update step).&lt;/p&gt;

&lt;h2&gt;
  
  
  Bindings
&lt;/h2&gt;

&lt;p&gt;The WebGPU mechanism for data exchange between the CPU to the GPU is bindings - a JS Array (like a &lt;code&gt;Float32Array&lt;/code&gt;) can be "bound" to a memory location in WGSL with a WebGPU &lt;code&gt;Buffer&lt;/code&gt;. The WGSL memory location is identified with two integers: a group number and a binding number.&lt;/p&gt;

&lt;p&gt;In our case, both the compute shader and the vertex shader rely on two data bindings: time and particle positions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time - uniforms
&lt;/h3&gt;

&lt;p&gt;The uniform definition exists in both the compute shader (&lt;a href="https://github.com/alexey-dc/webgpu_html/blob/main/000_compute_vertex_fragment.html#L43" rel="noopener noreferrer"&gt;https://github.com/alexey-dc/webgpu_html/blob/main/000_compute_vertex_fragment.html#L43&lt;/a&gt;) and the vertex shader (&lt;a href="https://github.com/alexey-dc/webgpu_html/blob/main/000_compute_vertex_fragment.html#L69" rel="noopener noreferrer"&gt;https://github.com/alexey-dc/webgpu_html/blob/main/000_compute_vertex_fragment.html#L69&lt;/a&gt;) - the compute shader updates position, and the vertex shader updates color based on time.&lt;/p&gt;

&lt;p&gt;Let's take a look at the binding setup in JS and WGSL, starting with the compute shader.&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;computeBindGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createBindGroup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="cm"&gt;/*
    see computePipeline definition at
    https://github.com/alexey-dc/webgpu_html/blob/main/000_compute_vertex_fragment.html#L102

    it allows linking a JS string with WGSL code to WebGPU
  */&lt;/span&gt;
  &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;computePipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBindGroupLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// group number 0&lt;/span&gt;
  &lt;span class="na"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="c1"&gt;// time bound at binding number 0&lt;/span&gt;
    &lt;span class="na"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="cm"&gt;/*
      for reference, buffer declared as:

      const timeBuffer = device.createBuffer({
        size: Float32Array.BYTES_PER_ELEMENT,
        usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST})
      })

      https://github.com/alexey-dc/webgpu_html/blob/main/000_compute_vertex_fragment.html#L129
      */&lt;/span&gt;
      &lt;span class="na"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;timeBuffer&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="c1"&gt;// particle position data at binding number 1 (still in group 0)&lt;/span&gt;
    &lt;span class="na"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;particleBuffer&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 corresponding declarations in the compute shader&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="c1"&gt;// From the compute shader - similar declaration in vertex shader&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;uniform&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;read_write&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;particles&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Particle&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Importantly, we bind the JS-side &lt;code&gt;timeBuffer&lt;/code&gt; to WGSL by matching the group and binding numbers in JS and WGSL.&lt;/p&gt;

&lt;p&gt;This gives us the power to control the variable's value from JS:&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;/* Just need 1 element in the array since time is a single float value */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeJs&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;Float32Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;5.3&lt;/span&gt;
&lt;span class="cm"&gt;/* Plain JS, just set the value */&lt;/span&gt;
&lt;span class="nx"&gt;timeJs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="cm"&gt;/* Pass the data from CPU/JS to GPU/WGSL */&lt;/span&gt;
&lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeJs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Particle positions - WGSL storage
&lt;/h3&gt;

&lt;p&gt;We store and update the particle positions directly in GPU-accessible memory - allowing us to update them in parallel relying on the massive multi-core architecture of the GPU.&lt;/p&gt;

&lt;p&gt;The parallelization is orchestrated with the help of a workgroup size, declared in the compute shader:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;compute&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;workgroup_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;builtin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;global_invocation_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;global_id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vec3&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;u32&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="c1"&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 &lt;code&gt;@builtin(global_invocation_id) global_id : vec3&amp;lt;u32&amp;gt;&lt;/code&gt; value gives a thread identifier.&lt;/p&gt;

&lt;p&gt;By definition, &lt;code&gt;global_invocation_id = workgroup_id * workgroup_size + local_invocation_id&lt;/code&gt; - which means it can be used as a particle index.&lt;/p&gt;

&lt;p&gt;For example, if we have 10k particles, and a &lt;code&gt;workgroup_size&lt;/code&gt; of 64, we'll need to dispatch &lt;code&gt;Math.ceil(10000/64)&lt;/code&gt; workgroups. We'll explicitly tell the GPU to do that amount of work each time we trigger a compute pass from JS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;computePass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchWorkgroups&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PARTICLE_COUNT&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;WORKGROUP_SIZE&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;PARTICLE_COUNT == 10000&lt;/code&gt; and &lt;code&gt;WORKGROUP_SIZE == 64&lt;/code&gt;, we'll launch 157 workgroups (10000/64 = 156.25), and each will compute ranging &lt;code&gt;local_invocation_id&lt;/code&gt; from 0 to 63 (while &lt;code&gt;workgroup_id&lt;/code&gt; will range from 0 to 157). We'll end up doing slightly more calculations in one of the workgroups, since 157 * 64 = 1048. We deal with the overflow by discarding the extraneous invocations.&lt;/p&gt;

&lt;p&gt;Here's what the compute shader ends up looking with those considerations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;compute&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;workgroup_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;WORKGROUP_SIZE&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;builtin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;global_invocation_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;global_id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vec3&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;u32&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;global_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Discard extra computations due to workgroup grid misalignment&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;index&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nf"&gt;arrayLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;particles&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="cm"&gt;/* Convert integer index to float so we can compute position updates based on index (and time)*/&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;fi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    
  &lt;span class="nx"&gt;particles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vec2&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;f32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="cm"&gt;/* No grand intent behind the formulas - just an example of using time+index */&lt;/span&gt;
    &lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;fi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;fi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;10&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;These values will persist across compute passes, because &lt;code&gt;particles&lt;/code&gt; are defined as a &lt;code&gt;storage&lt;/code&gt; var.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading particles positions from compute shader in vertex shader
&lt;/h3&gt;

&lt;p&gt;To read the particle positions in the vertex shader from the compute shader, we'll need a read-only view into the data, since only compute shaders are allowed to write to storage.&lt;/p&gt;

&lt;p&gt;Here's the WGSL declaration for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;uniform&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;particles&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;vec2&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;f32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cm"&gt;/*
Or equivalent:

@group(0) @binding(1) var&amp;lt;storage, read&amp;gt; particles : array&amp;lt;vec2&amp;lt;f32&amp;gt;&amp;gt;;
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Attempting to re-use the same &lt;code&gt;read_write&lt;/code&gt; style from the compute shader would just error out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var with 'storage' address space and 'read_write' access mode cannot be used by vertex pipeline stage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the binding numbers in the vertex shader don't have to match the compute shader binding numbers - they just need to match whatever the declaration for the bind group for the vertex shader are:&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;renderBindGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createBindGroup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBindGroupLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;timeBuffer&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="na"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;particleBuffer&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;I opted for  &lt;code&gt;binding: 2&lt;/code&gt; in the GitHub sample code &lt;a href="https://github.com/alexey-dc/webgpu_html/blob/main/000_compute_vertex_fragment.html#L70" rel="noopener noreferrer"&gt;https://github.com/alexey-dc/webgpu_html/blob/main/000_compute_vertex_fragment.html#L70&lt;/a&gt; - just as a matter of exploring the boundaries of the constraints imposed by WebGPU&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Running the simulation step by step
&lt;/h2&gt;

&lt;p&gt;After all the setup is in place, the update-and-render loop is orchestrated in JS:&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;/* Start simulation at t = 0*/&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/*
    Use constant integer timesteps for simplicity - will render consistently regardless of framerate.
  */&lt;/span&gt;
  &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;timeJs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeJs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Compute pass to update particle positions&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;computePassEncoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCommandEncoder&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;computePass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;computePassEncoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beginComputePass&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;computePass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;computePipeline&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;computePass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setBindGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;computeBindGroup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Important to dispatch the right number of workgroups to process all particles&lt;/span&gt;
  &lt;span class="nx"&gt;computePass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchWorkgroups&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PARTICLE_COUNT&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;WORKGROUP_SIZE&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;computePass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;computePassEncoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;

  &lt;span class="c1"&gt;// Render pass&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;commandEncoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCommandEncoder&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;passEncoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;commandEncoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beginRenderPass&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;colorAttachments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCurrentTexture&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;createView&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;clearValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;loadOp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clear&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;storeOp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;store&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="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;passEncoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;passEncoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setBindGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;renderBindGroup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;passEncoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PARTICLE_COUNT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;passEncoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;commandEncoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;

  &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;WebGPU unlocks the power of massively parallel GPU computations in the browser.&lt;/p&gt;

&lt;p&gt;It operates in passes - each pass having localized variables, enabled through pipelines with memory bindings (bridging CPU memory and GPU memory).&lt;/p&gt;

&lt;p&gt;Compute passes allow orchestrating parallel workloads through workgroups.&lt;/p&gt;

&lt;p&gt;While it does require some heavy set up, in my humble opinion the local binding/state style is a huge improvement over the global state model of WebGL - making it much easier to work with, while also finally bringing the power of GPU compute to the web.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webgpu</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Webapps need app-like monetization</title>
      <dc:creator>Alexey</dc:creator>
      <pubDate>Sun, 19 Jun 2022 01:22:00 +0000</pubDate>
      <link>https://dev.to/alexeydc/a-free-world-of-app-publishing-1a4l</link>
      <guid>https://dev.to/alexeydc/a-free-world-of-app-publishing-1a4l</guid>
      <description>&lt;p&gt;I wrote my first iOS app in 2010 while still in college - it was a zynga-style social RPG called Shovel Heroes.&lt;/p&gt;

&lt;p&gt;The app store was a beautiful innovation: a new way to distribute software in the age of internet. One important part of the innovation was monetization built into the ecosystem - since Apple keeps the credit card tied to the device.&lt;/p&gt;

&lt;p&gt;The app store unleashed an explosion of applications, because seamless distribution on a monetizable channel created the financial incentive, entirely new opportunities.&lt;/p&gt;

&lt;p&gt;However, Apple has treated developers terribly with fees, gatekeeping, lack of support, poor documentation, and lackluster tooling.&lt;/p&gt;

&lt;p&gt;Developing apps has always been expensive and laborious, and sadly native development has only gotten worse over time: XCode overheats my $4000 M1 Pro Max laptop after just 15 minutes, causing glitchy/laggy performance of the entire handheld supercomputer.&lt;/p&gt;

&lt;p&gt;Recently, I built &lt;a href="https://spicebreaker.app" rel="noopener noreferrer"&gt;Spicebreaker&lt;/a&gt; - at first as a native app built with React Native, but eventually I re-wrote from scratch on the web.&lt;/p&gt;

&lt;p&gt;The main punchline of this post is that payments, identity, and distribution are the major advantages of the app store over the web as a publishing platform, and that crypto wallets have the potential to solve for two of those problems on the web. If you wish, skip through the dev story and check out the UX crypto enables.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pivoting from native mobile app to web
&lt;/h1&gt;

&lt;p&gt;Much of what I say here primarily affects indie developers - large companies still suffer, but they have the budget to power through it.&lt;/p&gt;

&lt;p&gt;On a limited budget, a cross-platform solution is that much more desirable.&lt;/p&gt;

&lt;p&gt;I wrote a prototype in React Native on a weekend, and wrote a content management system on another weekend, which my wife used to lead the content creation. It took a few months to playtest it, iterate on the core concepts. After maybe 6 months it was ready to launch on mobile, perhaps needing a few finishing touches.&lt;/p&gt;

&lt;p&gt;I thought it was one a finish line to launch, but ultimately I abandoned the React Native project and launched on web.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues with iOS
&lt;/h2&gt;

&lt;p&gt;I've worked with many developer tools in my career: Sublime, Eclipse, Visual Studio, Vim, Jetbrains/IntelliJ, VSCode, Ruby Mine, even BorlandC++/BorlandPascal, Delphi, Notepad++... &lt;a href="https://www.reddit.com/r/swift/comments/qw7zj1/why_the_hate_on_xcode/" rel="noopener noreferrer"&gt;XCode&lt;/a&gt; is the &lt;a href="https://www.quora.com/Why-do-people-still-hate-Xcode-as-an-IDE-What-features-is-it-lacking-in" rel="noopener noreferrer"&gt;absolute&lt;/a&gt; &lt;a href="https://news.ycombinator.com/item?id=13973601" rel="noopener noreferrer"&gt;worst&lt;/a&gt; &lt;a href="https://www.hkgant.com/i-hate-xcode/" rel="noopener noreferrer"&gt;developer&lt;/a&gt; &lt;a href="https://www.reddit.com/r/iOSProgramming/comments/fmys59/xcode_is_worst_ide_i_have_ever_used/" rel="noopener noreferrer"&gt;tool&lt;/a&gt; &lt;a href="https://www.reddit.com/r/ProgrammerHumor/comments/d6uqcz/i_hate_xcode_so_much/" rel="noopener noreferrer"&gt;I have ever worked with&lt;/a&gt;. Sorry. Not sorry.&lt;/p&gt;

&lt;p&gt;I've squandered hundreds of precious hours of my life dealing with arcane XCode issues throughout my career. There's no real understanding/pattern or depth behind dealing with them, and support is close to non-existent.&lt;/p&gt;

&lt;p&gt;One example during Spicebreaker's development was the pain of updating the SQLite database with my questions. It's just a file - I expected to override it, and have the app pick up the new version, but it doesn't work that way.&lt;/p&gt;

&lt;p&gt;I tried various solutions, including &lt;a href="https://github.com/andpor/react-native-sqlite-storage/issues/322" rel="noopener noreferrer"&gt;deleting the database&lt;/a&gt; before loading it - but not even that worked to reset the cache. After wasting more hours than it took to build the prototype, I settled on renaming the database file each time I update it.&lt;/p&gt;

&lt;p&gt;The key point I want to drive home is not this specific minor issue, but that hundreds of little issues like this slow down development and drain the joy from making apps.&lt;/p&gt;

&lt;p&gt;At a certain point, I bought a new M1 laptop (my old Mac Pro had developed debilitating overheating issues). XCode and iOS are famous for incompatibility issues, and sure enough my build broke with arcane inexplicable errors. I had to start a fresh app, and slowly carry over features from the old app, over 3 days on Christmas break - which had meant a 9 month pause on any development progress since the app wasn't building.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apple's corporate attitude towards developers
&lt;/h3&gt;

&lt;p&gt;I tried to register a special email for Spicebreaker's Apple developer account. Apple rejected every credit card my wife and I put into the registration. It took days to even reach a rejection, and support had nothing to offer us in help. We ended up using my wife's personal Apple account for the developer program.&lt;/p&gt;

&lt;p&gt;Publishing a Beta on TestFlight requires a review/approval from Apple, and my first review failed with no note. How is that even possible? This is a Beta, not a release. Why do I need review?&lt;/p&gt;

&lt;h3&gt;
  
  
  Last straw
&lt;/h3&gt;

&lt;p&gt;So in these 2 years the &lt;a href="https://en.wikipedia.org/wiki/Epic_Games_v._Apple" rel="noopener noreferrer"&gt;Epic vs Apple&lt;/a&gt; trial had unraveled. And one of the big arguments Apple made was that they are owed the 30% commission because they provide highly valuable tools to developers.&lt;/p&gt;

&lt;p&gt;Given the above rants, I hope my passionately violent gut reaction can be understood through a rational lens.&lt;/p&gt;

&lt;p&gt;I would pay lots and lots of money to not have to deal with their developer tools. I would pay a different app store 50% commission if only I got to not have to deal with XCode, TestFlight, the Apple Developer Program, and their review process. Apple fights vigorously against the possibility, though - obviously they are in no shape to compete.&lt;/p&gt;

&lt;p&gt;I needed a website to promote the app. I started up a basic &lt;a href="https://dev.to/alexeydc/pm2-express-nextjs-with-github-source-zero-downtime-deploys-n71"&gt;Express+NextJS&lt;/a&gt; server, and... re-built the entire game, designs included, in a day or two on a weekend. Oh, and by the way, I no longer have to deal with any of those SQLite issues - because I'm directly connected to my database by default. No build issues. No deploy issues. No issues publishing a Beta, no issues sharing the game with my friends. Christ almighty, what a breath of fresh air.&lt;/p&gt;

&lt;p&gt;I just want to reiterate that these considerations become maximally important with a constrained budget, though arguably cheaper is always better as long as quality isn't compromised.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a&gt;Switching to web&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;The pivot to web made me reflect on what pushed me to write an app in the first place.&lt;/p&gt;

&lt;p&gt;Mobile apps have done very well in monetization. Mobile has nearly eliminated the friction of payments: on both the app store and inside apps, payments are just a few interactions away.&lt;/p&gt;

&lt;p&gt;So what's the problem on the web? There's Stripe, PayPal - I can ~easily integrate payments. Why is the web an inferior payment platform for applications like Spicebreaker?&lt;/p&gt;

&lt;h2&gt;
  
  
  Payments and identity
&lt;/h2&gt;

&lt;p&gt;The friction of payments on the web is identity.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Physical goods&lt;/em&gt; &lt;em&gt;don't require&lt;/em&gt; a digital identity per se - but you'd need to put in your address each time you make a purchase without one.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Digital goods&lt;/em&gt;, on the other hand, absolutely &lt;em&gt;require&lt;/em&gt; a digital identity, since their ownership is impossible otherwise. They represent most purchases on the app store: content subscriptions, in-game currency, unlockable features.&lt;/p&gt;

&lt;p&gt;Spicebreaker sells packs of questions, i.e. digital content; to prove you've made a purchase and claim that digital content a year later on a different device, you need to be able to authenticate with a repeatable credential.&lt;/p&gt;

&lt;p&gt;The need to set up those repeatable credentials creates the crux of the friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identity on the web
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The problem
&lt;/h3&gt;

&lt;p&gt;Modern mobile devices provide identity by default: e.g. Apple ID or Google Account. That identity is shared by all apps, e.g. in-app purchases are tied to your Apple ID on iOS, and can be recovered when switching devices. The key is that the setup is done just once - when setting up the phone - and then consumed by all apps.&lt;/p&gt;

&lt;p&gt;The web does not provide a cohesive identity platform: each account-based website is ignorant of your login information on other websites. This is by design, of course. OAuth is the strongest modern solution to the problem, but the experience of OAuth + Payment is not nearly as seamless as platform-based payment identity. OAuth also forces you to reveal your web activity to OAuth providers.&lt;/p&gt;

&lt;p&gt;Today, browser-based web identity does not yet exist. Until recently, the only way to purchase digital goods on the web and retain ownership was to (ugh) Register an Account. This enormous friction is a major reason the web has not yet become a vibrant app publishing platform the way the app store is.&lt;/p&gt;

&lt;h3&gt;
  
  
  The solution
&lt;/h3&gt;

&lt;p&gt;There already exists a technology that links identity and payments on the web that can act as a platform for applications: web3 wallet plugins. Wallet plugins need to be set up once per browser, and can unify identity across web sites via public keys (and private key signatures verifying ownership of those public keys).&lt;/p&gt;

&lt;p&gt;If that sounds complicated, please check out the &lt;a href="https://spicebreaker.app/login_signup" rel="noopener noreferrer"&gt;login&lt;/a&gt; flow on Spicebreaker. The UX itself is dead simple (that's the whole point):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click a button to begin sign in process with Phantom&lt;/li&gt;
&lt;li&gt;Click "Approve" inside of a Phantom Wallet popup&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7joezzvzpv75ol7pmfwn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7joezzvzpv75ol7pmfwn.png" alt="Image description" width="800" height="455"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcxsvivlouxky621t292.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcxsvivlouxky621t292.png" alt="Image description" width="734" height="1158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are also more technical details below.&lt;/p&gt;

&lt;p&gt;The purchase is done via &lt;a href="https://www.circle.com/en/usdc" rel="noopener noreferrer"&gt;USDC&lt;/a&gt;, so it's always $10. The purchase is a &lt;a href="https://spicebreaker.app/checkout_with_phantom" rel="noopener noreferrer"&gt;3-step experience&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connect Phantom Wallet&lt;/li&gt;
&lt;li&gt;Click "Purchase"&lt;/li&gt;
&lt;li&gt;Confirm payment in Phantom Wallet popup&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a&gt;Here's what it looks like:&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhm3ptr02p19ww1rgrio0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhm3ptr02p19ww1rgrio0.png" alt="Image description" width="800" height="461"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F34l0az8l8a5c9eyd6guu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F34l0az8l8a5c9eyd6guu.png" alt="Image description" width="800" height="647"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9t7k25osdkaywhgwc5uv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9t7k25osdkaywhgwc5uv.png" alt="Image description" width="726" height="1164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reason I chose Phantom wallet for this technical experiment is that the Solana blockchain facilitates extremely low fees (fractions of a cent). There are certainly alternatives out there (e.g. Polygon), and I look forward to trying them out in the future.&lt;/p&gt;

&lt;p&gt;Low fees are extremely important for an application whose most expensive purchase is $10. Ethereum gas fees ($3-$100) are exorbitantly high for in-app purchase applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical details
&lt;/h3&gt;

&lt;p&gt;How does the web3 identity/purchase solution work, exactly?&lt;/p&gt;

&lt;p&gt;The gist of it is that a purchase is tied to a public key, and access is authenticated via a private key signature - the same way blockchains authorize transactions, except done on top of a traditional centralized database application.&lt;/p&gt;

&lt;p&gt;A wallet is able to issue signatures and share the public key. &lt;a&gt;So the mechanism is as follows&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The front end requests authentication for a public key via an API call&lt;/li&gt;
&lt;li&gt;The API call returns some random string of data&lt;/li&gt;
&lt;li&gt;The wallet issues a signature for that random string of data&lt;/li&gt;
&lt;li&gt;The front end resolves authentication by providing the signature via an API call&lt;/li&gt;
&lt;li&gt;If the signature is correct, the backend issues a session for the front end&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A purchase is automatically tied to a public key, since every transaction sent to the blockchain contains information on which public key triggered it - so without any extra effort the purchase is tied to your identity, and you are able to prove your identity as long as you have the corresponding private key. Which you absolutely should always have if that's where your money is stored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distribution
&lt;/h2&gt;

&lt;p&gt;Perhaps crypto wallets can solve identity and payments, but what about distribution - the third pillar of the App Store?&lt;/p&gt;

&lt;p&gt;It's great to be promoted by the app store - winning a top award is a sure way to get millions of users.&lt;/p&gt;

&lt;p&gt;The web doesn't have a single natural distribution platform. The distribution platforms of the web are SEO, social networks, bloggers, influencers etc.&lt;/p&gt;

&lt;p&gt;There's no one way to play growth... but perhaps winning on the App Store is not easier than winning on a web distribution platform!&lt;/p&gt;

&lt;h2&gt;
  
  
  Harsh modern reality
&lt;/h2&gt;

&lt;p&gt;Web3 and crypto are not mainstream.&lt;/p&gt;

&lt;p&gt;I was extremely happy to imagine (and implement) a future with web payments as seamless as on the app store.&lt;/p&gt;

&lt;p&gt;Today, the harsh reality is that a mass audience is not going to pay that way. So ultimately, Spicebreaker supports creating accounts via email, and paying with PayPal. I don't collect any personal info, and don't store passwords - the email sends a one-time code each login attempt, which I find much more usable than passwords. I'll eventually add various OAuth providers to also help with sign-up friction.&lt;/p&gt;

&lt;p&gt;The UX isn't terrible, but it's nothing like the single-click seamless experience with wallets, or with in-app purchases.&lt;/p&gt;

&lt;p&gt;There are many advances in the space. &lt;a href="https://brave.com/" rel="noopener noreferrer"&gt;Brave browser&lt;/a&gt; is pioneering work on becoming a browser-based identity and payments platform, and they are getting traction with users. Even Chrome now supports identity in the browser - though it's to your Google Account, which these days is one of the least private types of accounts you can link to an application. Would you want Google to know what purchases you make on every web page?&lt;/p&gt;

&lt;p&gt;In that world, as an indie developer, I would realistically integrate with the most used and least friction-prone payments platforms on the web; I'd grind my teeth but integrate with Google - so true privacy may be a luxury until crypto is adopted in the mainstream.&lt;/p&gt;

&lt;p&gt;My hope is that web3 authentication and payments do become more mainstream, and wallets dominate web payments. That way, independent developers like myself can publish to an open, privacy-respecting, universally accessible platform, where we can use the best developer tools in existence: the web.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>webdev</category>
      <category>web3</category>
      <category>showdev</category>
    </item>
    <item>
      <title>PM2 + Express + NextJS (with GitHub source): zero downtime deploys</title>
      <dc:creator>Alexey</dc:creator>
      <pubDate>Sat, 11 Jun 2022 20:12:23 +0000</pubDate>
      <link>https://dev.to/alexeydc/pm2-express-nextjs-with-github-source-zero-downtime-deploys-n71</link>
      <guid>https://dev.to/alexeydc/pm2-express-nextjs-with-github-source-zero-downtime-deploys-n71</guid>
      <description>&lt;p&gt;This article builds on a &lt;a href="https://dev.to/alexeydc/express-nextjs-sample-tutorial-integration-485f"&gt;previous article&lt;/a&gt; of mine, which introduced a basic Express+NextJS setup that enabled hosting both a React-based front end and API on one service - reducing distributed system hassles.&lt;/p&gt;

&lt;p&gt;This article moves that setup closer to production by introducing zero-downtime backend deploys via &lt;a href="https://pm2.keymetrics.io/" rel="noopener noreferrer"&gt;PM2&lt;/a&gt;. We'll also introduce logging for this setup via &lt;a href="https://www.npmjs.com/package/log4js" rel="noopener noreferrer"&gt;log4js&lt;/a&gt;, since it requires initializing PM2 in a log-compatible way.&lt;/p&gt;

&lt;p&gt;I've deployed this project as a demo on an EC2 instance in AWS: &lt;a href="https://nextjs-express.alexey-dc.com/" rel="noopener noreferrer"&gt;https://nextjs-express.alexey-dc.com/&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  The source code
&lt;/h1&gt;

&lt;p&gt;Like the previous template, I open sourced this under the MIT license - so you're free to use it for commercial and closed-source projects, and I would of course appreciate attribution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/alexey-dc/pm2_nextjs_express_template" rel="noopener noreferrer"&gt;https://github.com/alexey-dc/pm2_nextjs_express_template&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The details for launching can be found in the &lt;a href="https://github.com/alexey-dc/pm2_nextjs_express_template#about" rel="noopener noreferrer"&gt;README.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It inherits the same basic setup/pages, but has more sophisticated configuration on launch - that works with PM2. I'll dive into a few details here.&lt;/p&gt;

&lt;h1&gt;
  
  
  Zero downtime deploys
&lt;/h1&gt;

&lt;p&gt;Two of the most common strategies for deploys without downtime are &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/overview-deployment-options/bluegreen-deployments.html" rel="noopener noreferrer"&gt;blue-green deployments&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/overview-deployment-options/rolling-deployments.html" rel="noopener noreferrer"&gt;rolling deployments&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;PM2 enables rolling deployments on a single machine.&lt;/p&gt;

&lt;p&gt;This is possible because it allows running multiple threads running the same server code via &lt;a href="https://pm2.keymetrics.io/docs/usage/cluster-mode/" rel="noopener noreferrer"&gt;cluster mode&lt;/a&gt;, which can be replaced one by one.&lt;/p&gt;

&lt;p&gt;Here's an example of a sequence of commands that can achieve a rolling update with PM2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Launch 2 instances of a server defined under index.js (-i 2)&lt;/span&gt;
pm2 start index.js &lt;span class="nt"&gt;--name&lt;/span&gt; pm2_nextjs_express &lt;span class="nt"&gt;-i&lt;/span&gt; 2
&lt;span class="c"&gt;# Perform rolling update with the latest code:&lt;/span&gt;
&lt;span class="c"&gt;# First kill and replace the first instance, then the second&lt;/span&gt;
pm2 reload pm2_nextjs_express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Graceful PM2 setup
&lt;/h1&gt;

&lt;p&gt;Here's how the template &lt;a href="https://github.com/alexey-dc/pm2_nextjs_express_template/blob/main/package.json#L28" rel="noopener noreferrer"&gt;actually launches&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 start index.js &lt;span class="nt"&gt;--name&lt;/span&gt; pm2_nextjs_express &lt;span class="nt"&gt;--wait-ready&lt;/span&gt; &lt;span class="nt"&gt;--kill-timeout&lt;/span&gt; 3000 &lt;span class="nt"&gt;-i&lt;/span&gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are 2 additional flags: &lt;code&gt;--wait-ready&lt;/code&gt; and &lt;code&gt;--kill-timeout&lt;/code&gt; - they allow graceful booting and cleaning up.&lt;/p&gt;

&lt;p&gt;Let's take a look at some key bits from &lt;a href="https://github.com/alexey-dc/pm2_nextjs_express_template/blob/main/index.js" rel="noopener noreferrer"&gt;index.js&lt;/a&gt; - which works with those flags. I've slightly modified the code here to focus on the points being made, but you can always read the real source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Graceful setup
&lt;/h2&gt;

&lt;p&gt;We let PM2 know that we've completed setup by sending a &lt;code&gt;process.send('ready')&lt;/code&gt; signal after all the configuration:&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;begin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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="c1"&gt;//  ...&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&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;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EXPRESS_PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="cm"&gt;/*
    Let pm2 know the app is ready
    https://pm2.keymetrics.io/docs/usage/signals-clean-restart/
  */&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ready&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="c1"&gt;//  ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Graceful teardown
&lt;/h2&gt;

&lt;p&gt;During shutdown, PM2 sends a &lt;code&gt;SIGINT&lt;/code&gt; signal, and expects us to &lt;code&gt;process.exit()&lt;/code&gt;; it waits for &lt;code&gt;--kill-timeout&lt;/code&gt; (3000ms in our case), and the sends a &lt;code&gt;SIGKILL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So to respect that lifecycle and perform cleanup, we listen for the &lt;code&gt;SIGINT&lt;/code&gt; signal, perform cleanup, and exit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGINT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Logging
&lt;/h1&gt;

&lt;p&gt;Since PM2 runs on multiple threads, logging can be challenging. This is why I've included a sample integration of PM2+Log4js.&lt;/p&gt;

&lt;p&gt;That &lt;a href="https://github.com/log4js-node/log4js-node/blob/master/docs/clustering.md#im-using-pm2-but-im-not-getting-any-logs" rel="noopener noreferrer"&gt;does not work out of the box&lt;/a&gt; - but log4js explicitly supports a &lt;code&gt;{pm2: true}&lt;/code&gt; flag in its configuration.&lt;/p&gt;

&lt;p&gt;The log4js docs mention that &lt;a href="https://www.npmjs.com/package/pm2-intercom" rel="noopener noreferrer"&gt;pm2-intercom&lt;/a&gt; is necessary to support this. Using that as-is gives an error due to the &lt;code&gt;process.send('ready')&lt;/code&gt; message we send, however:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  4|pm2-intercom  | Error: ID, DATA or TOPIC field is missing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Luckily, there is a fork of pm2-intercom that explicitly addresses this issue &lt;a href="https://www.npmjs.com/package/pm2-graceful-intercom" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/pm2-graceful-intercom&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've also &lt;a href="https://github.com/alexey-dc/pm2_nextjs_express_template/blob/main/app/blib/log.js#L13" rel="noopener noreferrer"&gt;documented this in detail&lt;/a&gt; in the log configuration included with the project.&lt;/p&gt;

&lt;h1&gt;
  
  
  Debugging
&lt;/h1&gt;

&lt;p&gt;I've included a setup for &lt;a href="https://github.com/alexey-dc/pm2_nextjs_express_template/blob/main/package.json#L32" rel="noopener noreferrer"&gt;debugging&lt;/a&gt; as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# This will run on `pnpm debug`&lt;/span&gt;
pm2 start index.js &lt;span class="nt"&gt;--name&lt;/span&gt; pm2_nextjs_express_debug &lt;span class="nt"&gt;--wait-ready&lt;/span&gt; &lt;span class="nt"&gt;--kill-timeout&lt;/span&gt; 3000 &lt;span class="nt"&gt;--node-args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'--inspect-brk'&lt;/span&gt;
&lt;span class="c"&gt;# This will run on `pnpm stop_debug`&lt;/span&gt;
pm2 delete pm2_nextjs_express_debug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--node-args='inspect-brk'&lt;/code&gt; flag enables debugging via a socket connection. It's a &lt;a href="https://nodejs.org/en/docs/guides/debugging-getting-started/#command-line-options" rel="noopener noreferrer"&gt;standard node flag&lt;/a&gt;. One great way to work with that debug mode is via Chrome's chrome://inspect. If you don't want to use chrome, just see the official &lt;a href="https://nodejs.org/en/docs/guides/debugging-getting-started/" rel="noopener noreferrer"&gt;Node.js docs&lt;/a&gt; for more options.&lt;/p&gt;

&lt;p&gt;You'll notice I don't enable cluster mode for debugging - that's because it &lt;a href="https://github.com/Unitech/pm2/issues/3070" rel="noopener noreferrer"&gt;doesn't work well&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You'll also notice I launch it on a separate name, don't offer a reload, and the stop involves deleting the process from PM2, vs stopping it - like for the normal run mode. The main reason I did that is because the breakpoints can cause issues for restarts - PM2 will print errors and refuse to boot, and you'll end up having to manually delete the process anyway.&lt;/p&gt;

&lt;h1&gt;
  
  
  Async configuration
&lt;/h1&gt;

&lt;p&gt;One other opinionated feature I've included in this template is a global namespace for re-usable code.&lt;/p&gt;

&lt;p&gt;The reason I did that is two-fold:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There are very often globally configured resources, like database connections, that are shared all across the application - that require async setup when the application launches&lt;/li&gt;
&lt;li&gt;There is also often utility code that is shared across the application - that is useful in other contexts, e.g. the debugger (or a repl console)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are other ways of achieving this than making a global namespace - but I thought it may be more informative to show a specific style of async setup with PM2/Express.&lt;/p&gt;

&lt;p&gt;So here is the thinking behind what's going on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The global backend utility namespace
&lt;/h2&gt;

&lt;p&gt;I expose a &lt;code&gt;global.blib&lt;/code&gt; namespace - which is not &lt;code&gt;global.lib&lt;/code&gt;, specifically because this setup combines NextJS with Express: with NextJS SSR, React code runs on the backend - thus, if &lt;code&gt;lib&lt;/code&gt; is defined on the backend and front end, there will actually be a naming conflict leading to surprising results.&lt;/p&gt;

&lt;p&gt;All re-usable/shared backend code lives under &lt;code&gt;app/blib&lt;/code&gt;. The logic of pulling in the library is housed under &lt;code&gt;app/blib/_blib.js&lt;/code&gt;, so the responsibility of keeping track of files can be encapsulated in the module. Another way of achieving this would be with a &lt;code&gt;package.json&lt;/code&gt; file - but I opted for raw JS.&lt;/p&gt;

&lt;p&gt;One reason the raw JS is handy is because the initialization logic works well in that same &lt;code&gt;_blib.js&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Other than pulling in libraries, it also exposes &lt;code&gt;async init()&lt;/code&gt;  and &lt;code&gt;aynsc cleanup()&lt;/code&gt; functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up and tearing down the library
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;init&lt;/code&gt; and &lt;code&gt;cleanup&lt;/code&gt; functions naturally plug into the PM2 lifecycle discussed above.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;init&lt;/code&gt; runs before &lt;code&gt;process.send('ready')&lt;/code&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./app/blib/_blib.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="cm"&gt;/*
    If you don't like globals, you can always opt out of this.
    I find it easier to have consistent access across the application
    to often-invoked functionality.
  */&lt;/span&gt;
  &lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;blib&lt;/span&gt;
  &lt;span class="cm"&gt;/*
    This is the only other global I like to expose - since logging is
    most common and most verbose.
  */&lt;/span&gt;
  &lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;blib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="cm"&gt;/*
    Usually this will at least open database connections.
    In the sample code, a simple in-memory store is initialized instead.
  */&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;blib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&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;server&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;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EXPRESS_PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ready&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="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and &lt;code&gt;cleanup&lt;/code&gt; is done in the &lt;code&gt;SIGINT&lt;/code&gt; handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGINT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;blib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="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;log&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sonething went wrong during shutdown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>nextjs</category>
      <category>node</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Sessions: cookies vs localStorage - which one to use when</title>
      <dc:creator>Alexey</dc:creator>
      <pubDate>Sat, 12 Feb 2022 18:58:05 +0000</pubDate>
      <link>https://dev.to/alexeydc/sessions-cookies-vs-localstorage-2h7g</link>
      <guid>https://dev.to/alexeydc/sessions-cookies-vs-localstorage-2h7g</guid>
      <description>&lt;h1&gt;
  
  
  Preamble
&lt;/h1&gt;

&lt;p&gt;Have you ever debated whether to use cookies or localStorage for sessions?&lt;/p&gt;

&lt;p&gt;I have spent many hours on many projects contemplating which one to use. For example, I've found &lt;a href="https://stackoverflow.com/questions/26340275/where-to-save-a-jwt-in-a-browser-based-application-and-how-to-use-it/40376819#40376819"&gt;this guideline&lt;/a&gt; from a stackoverflow post compelling:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;If you create a Web application you need to deal with XSS; always, independently of where you store your tokens&lt;/li&gt;
&lt;li&gt;If you don't use cookie-based authentication CSRF should not even pop up on your radar so it's one less thing to worry about&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Generally, both options are considered viable. E.g. the &lt;a href="https://auth0.com/docs/secure/security-guidance/data-security/token-storage#browser-local-storage-scenarios"&gt;Auth0 team recommendation&lt;/a&gt; is making tokens stored in localStorage shorter lived.&lt;/p&gt;

&lt;p&gt;I was never fully satisfied with the distinction. After all my research it seemed the choice is somewhat arbitrary and the security argument isn't that strong; both can be made to work.&lt;/p&gt;

&lt;h1&gt;
  
  
  The dividing line
&lt;/h1&gt;

&lt;p&gt;There is a stronger decision boundary than the security argument. Security is just something you'll need to deal with depending on what you choose, but there is a dividing line rooted in functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: where localStorage wins: microservices, SPAs, pre-compiled HTML
&lt;/h2&gt;

&lt;p&gt;Suppose we have several API services that serve very different purposes - e.g. a payment service, and a persistence API - which operate on the same user database.&lt;/p&gt;

&lt;p&gt;Let's say the services require authentication with each API request.&lt;/p&gt;

&lt;p&gt;This immediately sets up a &lt;u&gt;dividing line&lt;/u&gt;: if the services live on different domains, cookies shouldn't be used (as they are not shared across domains).&lt;/p&gt;

&lt;p&gt;As long as all services live on the same domain, cookies can work fine... However, they will still cause issues with local development if microservices are run on different ports - since each port is its own domain. This is solvable e.g. with a local nginx setup, but that makes the configuration for running locally more complex.&lt;/p&gt;

&lt;p&gt;So, in a microservice context, localStorage/sessionStorage has these advantages over cookies for storing authentication credentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simpler local development for locally run microservices: different ports off localhost don't share cookies&lt;/li&gt;
&lt;li&gt;Cookies couple authentication logic and service architecture (by forcing services that share authentication onto a single domain), while localStorage does not&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 2: where cookies win: server-side rendering
&lt;/h2&gt;

&lt;p&gt;Now suppose you have a server that serves HTML from templates that it pre-populates with data.&lt;/p&gt;

&lt;p&gt;Some of that data may depend on your session - e.g. maybe we're just putting the user name into the page.&lt;/p&gt;

&lt;p&gt;In that case, cookie sessions are the way to go: the web page HTTP GET request that happens automatically when you load a URL in a browser does not leave room for injecting an authorization header [from localStorage], but comes with all the cookies for the domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: what if you have microservices AND want server-side rendering
&lt;/h2&gt;

&lt;p&gt;This was my exact case - using NextJS's SSR capabilities, and a standalone cross-application API service.&lt;/p&gt;

&lt;p&gt;I ended up backing out of server-side rendering (or rather pre-fetching log in data for the template), and instead exposing a &lt;code&gt;GET /profile&lt;/code&gt; endpoint that would get the session parsed out from headers via the common session middleware, and returns a JSON with the logged in/out state data.&lt;/p&gt;

&lt;p&gt;This impled a loading phase on the front end where after initial delivery, it sends out an HTTP request to fetch the profile, and only then finishes rendering.&lt;/p&gt;

&lt;p&gt;Of course it's also possible to write both localStorage and cookies, but I decided against that as I prefer having a single source of truth when possible - but if the preload state is very undesirable, it could be worth it.&lt;/p&gt;

&lt;p&gt;Or perhaps this consideration will make you think twice about extracting a microservice at all until it's really necessary!&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I find the above considerations very illuminating when considering whether I am ready to split out a microservice for an authenticated application, and gives me a better understanding of my toolbox of monoliths/microservices/SSR/SPAs/preloaders: I feel I am better able to build an elegant architecture based on the complexity of the project, since certain decisions pull in more and more complexity, and different forms of modularity come with different costs - for the architecture, developers, and end user.&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>security</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Little known vulnerability with ORMs and query builders</title>
      <dc:creator>Alexey</dc:creator>
      <pubDate>Wed, 27 Oct 2021 05:45:16 +0000</pubDate>
      <link>https://dev.to/alexeydc/little-known-vulnerability-with-sql-wrappers-loo</link>
      <guid>https://dev.to/alexeydc/little-known-vulnerability-with-sql-wrappers-loo</guid>
      <description>&lt;p&gt;This post is about an interesting problem that relates to crossing the boundary between a SQL database and code; spoiler alert: it has to do with a little known yet broadly relevant security pitfall.&lt;/p&gt;

&lt;h2&gt;
  
  
  The situation
&lt;/h2&gt;

&lt;p&gt;Suppose you have a user database with extremely sensitive data, and also data that is less sensitive.&lt;/p&gt;

&lt;p&gt;You wish to protect your most sensitive data, so you restrict access to it to some database users. Only privileged database credentials - available to a few micro-services and employees at the company - can grant read access to the data.&lt;/p&gt;

&lt;p&gt;For example, in a typical SQL database like Postgres, this is possible through &lt;code&gt;GRANT/REVOKE&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;secret_user_data&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;privileged_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;REVOKE&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;secret_user_data&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;basic_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;basic_user_data&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;basic_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;basic_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine how the primary keys work on those tables: &lt;code&gt;users&lt;/code&gt; has an ID for sure; &lt;code&gt;secret_user_data&lt;/code&gt; and &lt;code&gt;basic_user_data&lt;/code&gt; are 1-to-1 with users via a &lt;code&gt;user_id&lt;/code&gt; foreign key - so do they really need their own primary key?&lt;/p&gt;

&lt;p&gt;I would argue no, it would never be useful, because the &lt;code&gt;user_id&lt;/code&gt; column fully determines the row; you'd never need to query on a separate &lt;code&gt;id&lt;/code&gt; field. So these side tables should inherit the primary key from &lt;code&gt;users&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Now we're going to be writing this data from a server - be it Node, Sinatra, Flask, or any framework/language whatsoever.&lt;/p&gt;

&lt;p&gt;The problem that exposes is that of &lt;a href="https://en.wikipedia.org/wiki/Mass_assignment_vulnerability"&gt;mass assignment&lt;/a&gt;. For example, the Ruby ORM library Sequel goes to some lengths to &lt;a href="https://github.com/jeremyevans/sequel/blob/master/doc/mass_assignment.rdoc"&gt;deal with it&lt;/a&gt; - but most popular frameworks are in a similar boat.&lt;/p&gt;

&lt;p&gt;Mass assignment can happen when user input is used in write queries. For example, if your query builder takes in maps, and your POST endpoint accepts JSON which is converted to a map, perhaps whatever you put into the map gets written into the database.&lt;/p&gt;

&lt;p&gt;A particularly nasty accident can happen if the primary key field (in our case, &lt;code&gt;user_id&lt;/code&gt;) is overriden (imagine something like&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;db.secret_user_data.update({user_id: currentUser.id}, data)&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
, where &lt;code&gt;data = {user_id: -5}&lt;/code&gt;). Our side table entries would be orphaned from their users, and our data scrambled.&lt;/p&gt;
&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Sequel's solution is to "restrict primary keys": throw an exception if a primary key is included in a write. Perhaps your framework doesn't have this type of security measure in place - perhaps you even have the vulnerability somewhere in your codebase! But I'm sure all members of all your teams are responsible about writing only select fields and not the literal user input, even when pressed against deadlines.&lt;/p&gt;

&lt;p&gt;In the case I outlined, it's not possible to restrict primary keys. We need to actually write the primary key when we create the entry - but we definitely want the protection against overriding them after they are created.&lt;/p&gt;

&lt;p&gt;Luckily, this is easy to achieve at the database level. For example, in Postgres:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE OR REPLACE FUNCTION immutable_id()
RETURNS TRIGGER AS
  $BODY$
    BEGIN
      -- Alternatively, we can just fail silently
      -- NEW.id = OLD.id;
      IF NEW."id" IS DISTINCT FROM OLD."id"
      THEN
        RAISE EXCEPTION 'Refusing to update id column';
      END IF;
      RETURN NEW;
    END;
  $BODY$
LANGUAGE PLPGSQL;

CREATE TRIGGER secret_user_data_immutable_user_id
BEFORE UPDATE OF "user_id"
  ON "secret_user_data"
FOR EACH ROW
EXECUTE PROCEDURE immutable_id();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No restricted primary keys, no mass-assignment ID override vulnerability - win-win! We can have our granular security, minimal id structure, and any syntax we desire w/o fear of scrambling our data.&lt;/p&gt;

&lt;p&gt;The rest of the fields should probably still receive application-level care - but luckily that just means there's still a job for good engineers!&lt;/p&gt;

</description>
      <category>sql</category>
      <category>webdev</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Express + NextJS - sample/tutorial integration</title>
      <dc:creator>Alexey</dc:creator>
      <pubDate>Sun, 20 Jun 2021 21:41:00 +0000</pubDate>
      <link>https://dev.to/alexeydc/express-nextjs-sample-tutorial-integration-485f</link>
      <guid>https://dev.to/alexeydc/express-nextjs-sample-tutorial-integration-485f</guid>
      <description>&lt;h1&gt;
  
  
  Context
&lt;/h1&gt;

&lt;p&gt;While NextJS is a wonderful tool in its own right, augmenting it with Express makes for a powerful combo.&lt;/p&gt;

&lt;p&gt;One motivation may be simplicity - if you have a project you're trying to prototype and rapidly iterate on. These days, it's common to host the front end separately from the API, but then your project starts off as a distributed system - and you have to deal with &lt;a href="https://aws.amazon.com/builders-library/challenges-with-distributed-systems/" rel="noopener noreferrer"&gt;extra complexity&lt;/a&gt; up front.&lt;/p&gt;

&lt;p&gt;Some other use cases where it makes sense to do this type of combination:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enabling an existing Express API server to serve some front end with React/SSR&lt;/li&gt;
&lt;li&gt;Run some express middleware and fetch standard data for NextJS pages before they are served&lt;/li&gt;
&lt;li&gt;Adding custom logic to NextJS routing&lt;/li&gt;
&lt;li&gt;Adding WebSocket functionality (e.g. for a chat app)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This type of setup is documented in NextJS itself: &lt;a href="https://nextjs.org/docs/advanced-features/custom-server" rel="noopener noreferrer"&gt;https://nextjs.org/docs/advanced-features/custom-server&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the standard example, they use Node's &lt;code&gt;http&lt;/code&gt; package; we'll use Express to take advantage of its middleware and routing capabilities.&lt;/p&gt;

&lt;h1&gt;
  
  
  Source code
&lt;/h1&gt;

&lt;p&gt;I've provided an example barebones integration - as a github template - at &lt;a href="https://github.com/alexey-dc/nextjs_express_template" rel="noopener noreferrer"&gt;https://github.com/alexey-dc/nextjs_express_template&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also wrote an article on how to make this type of setup production-ready with PM2: &lt;a href="https://dev.to/alexeydc/pm2-express-nextjs-with-github-source-zero-downtime-deploys-n71"&gt;https://dev.to/alexeydc/pm2-express-nextjs-with-github-source-zero-downtime-deploys-n71&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using that setup, I've hosted the demo at &lt;a href="https://nextjs-express.alexey-dc.com/" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://nextjs-express.alexey-dc.com/" rel="noopener noreferrer"&gt;https://nextjs-express.alexey-dc.com/&lt;/a&gt; (it's just the template run on a public URL). The main difference with the code explained here is the PM2 configuration, which I use for zero downtime deploys.&lt;/p&gt;

&lt;h1&gt;
  
  
  The integration
&lt;/h1&gt;

&lt;p&gt;Let's take a look at some highlights of this NextJS+Express setup.&lt;/p&gt;

&lt;p&gt;The main entry point is &lt;code&gt;index.js&lt;/code&gt;, which sets up the environment and delegates spinning up the server:&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="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&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;Server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./app/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;begin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EXPRESS_PORT&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server running in --- &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; --- on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EXPRESS_PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that I'm relying on &lt;code&gt;dotenv&lt;/code&gt; to load environment variables - e.g. &lt;code&gt;EXPRESS_PORT&lt;/code&gt;, &lt;code&gt;NODE_ENV&lt;/code&gt;, and a few others. You can see the full list of necessary environment variables in the README in the github repository.&lt;/p&gt;

&lt;p&gt;In the server, both &lt;code&gt;nextjs&lt;/code&gt; and &lt;code&gt;express&lt;/code&gt; are initialzed, along with express midleware and a custom NextjsExpressRouter I built to take the routing over from NextJS into our own hands:&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&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;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;middleware&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;Middleware&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;express&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;router&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;NextjsExpressRouter&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;express&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;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The middleware I included is quite barebones, but serves as an example of what you might have in a real application:&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;express&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;bodyParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;express&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;bodyParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlencoded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;extended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;express&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="nf"&gt;favicon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;favicon.png&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 NextjsExpressRouter is really the heart of the integration. Let's take a closer look.&lt;/p&gt;

&lt;h1&gt;
  
  
  NextjsExpressRouter
&lt;/h1&gt;

&lt;p&gt;The idea is to allow GET routes for pages to co-exist with API HTTP routes:&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;NextjsExpressRouter&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;express&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="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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&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;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&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="nf"&gt;initApi&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="nf"&gt;initPages&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="nf"&gt;initErrors&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;initApi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./routes/api.js&lt;/span&gt;&lt;span class="dl"&gt;"&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;express&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;initPages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./routes/pages.js&lt;/span&gt;&lt;span class="dl"&gt;"&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;express&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;next&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="cm"&gt;/* Some standard error handling is also included in the repo code */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I split out the API from the page routes into separate files, and I find that as the codebase grows, it helps to impose some sort of grouping or hierarchy on endpoints. Pages and API calls seem like the most basic organization. Note I made the &lt;code&gt;init()&lt;/code&gt; function async. In this case we don't need to run any I/O operations or other async initialization, but in the general case we might want to.&lt;/p&gt;

&lt;p&gt;For my larger projects, the API typically itself has several sub-groups, and sometimes pages do as well. In this sample project that has very few routes, the API and pages are a flat list of routes:&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../data/integer_memory_store.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api&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;express&lt;/span&gt;&lt;span class="p"&gt;)&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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&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;express&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="s2"&gt;/api/get&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="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="na"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&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;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/increment&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Obviously this is just a minimal sample API - all it does is allows you to read and increment an integer stored in memory on the server.&lt;/p&gt;

&lt;p&gt;Here's how the NextJS page routes are defined:&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../data/integer_memory_store.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Pages&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;express&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="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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&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;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&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="nf"&gt;initCustomPages&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="nf"&gt;initDefaultPages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;initCustomPages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* With a monolith api+frontend, it's possible to serve pages with preloaded data */&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;express&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;/preload_data&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pageParams&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&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;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&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="s2"&gt;`/preload_data`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="cm"&gt;/* Special-purpose routing example */&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;express&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;/large_or_small/:special_value&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;intValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&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;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;special_value&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="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intValue&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="k"&gt;this&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="nf"&gt;render&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="s2"&gt;`/invalid_value`&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;query&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="nx"&gt;intValue&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&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="k"&gt;this&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="nf"&gt;render&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="s2"&gt;`/special_small`&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;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&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;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&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="s2"&gt;`/special_large`&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;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;initDefaultPages&lt;/span&gt;&lt;span class="p"&gt;()&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;express&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;/&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="k"&gt;return&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;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&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="s2"&gt;`/main`&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;query&lt;/span&gt;&lt;span class="p"&gt;)&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;express&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;*&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="k"&gt;return&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;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&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="s2"&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;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The page routes showcase setting up a root &lt;code&gt;/&lt;/code&gt; path, and a fallback &lt;code&gt;*&lt;/code&gt; path - if we're not able to match the GET request, we default to what NextJS's standard behavior is: rendering pages by filename from the &lt;code&gt;/pages&lt;/code&gt; directory. This allows for a gentle extension of NextJS's built-in capabilities.&lt;/p&gt;

&lt;p&gt;There are 2 examples for custom routing.&lt;/p&gt;

&lt;p&gt;In the first example, we pre-load some data, and bake it into the page before serving it to the user. This may be useful to avoid an extra HTTP roundtrip after the page renders, and is difficult to pull off w/o a monolithic API+frontend setup as presented here.&lt;/p&gt;

&lt;p&gt;In the second example, we render different variants of a page depending on an integer value in the route - or an error, if the input is invalid. Perhaps a real application may fetch user data, and render it differently depending on some condition (e.g. the viewer's relationship with them) - and render an error if the user is not found.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using the template
&lt;/h1&gt;

&lt;p&gt;I licensed the code under MIT - which means you are free to use the template in closed-source and commercial products, and make any modifications you want. Please attribute/give credit if you are able to!&lt;/p&gt;

&lt;p&gt;It's also a template on github, which means you can just click a button and start a new repo based on &lt;a href="https://github.com/alexey-dc/nextjs_express_template" rel="noopener noreferrer"&gt;https://github.com/alexey-dc/nextjs_express_template&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f4cy78rn2yx8k7good6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f4cy78rn2yx8k7good6.png" alt="Screen Shot 2021-06-20 at 5.36.59 PM"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Running
&lt;/h2&gt;

&lt;p&gt;The instructions for running are in the &lt;a href="https://github.com/alexey-dc/nextjs_express_template#running" rel="noopener noreferrer"&gt;github repo&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Iterating
&lt;/h2&gt;

&lt;p&gt;You'll probably want to delete the sample custom endpoint and associated pages I provided - and start replacing them with your own!&lt;/p&gt;

&lt;p&gt;I included a sample organization for pages as well - the page roots are in &lt;code&gt;pages&lt;/code&gt; as nextjs mandates, but all the reusable &lt;code&gt;jsx&lt;/code&gt; is in &lt;code&gt;views&lt;/code&gt; - for the demo, I was using a common layout for pages, and the &lt;code&gt;Layout&lt;/code&gt; component is housed in &lt;code&gt;views&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>nextjs</category>
      <category>node</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Javascript repl console</title>
      <dc:creator>Alexey</dc:creator>
      <pubDate>Sun, 18 Apr 2021 17:25:27 +0000</pubDate>
      <link>https://dev.to/alexeydc/talk-to-your-computer-in-javascript-the-repl-console-4l4i</link>
      <guid>https://dev.to/alexeydc/talk-to-your-computer-in-javascript-the-repl-console-4l4i</guid>
      <description>&lt;h1&gt;
  
  
  Premise
&lt;/h1&gt;

&lt;p&gt;I often want to run ad hoc Javascript commands that rely on npm packages and custom classes I've written that work with a database/filesystem or wrap common logic. &lt;/p&gt;

&lt;p&gt;Node comes with repl (Read-Eval-Print Loop), and you can launch a simple repl console by just running &lt;code&gt;node&lt;/code&gt; with no arguments - the commands for it are documented in e.g. &lt;a href="https://nodejs.org/api/repl.html#repl_design_and_features" rel="noopener noreferrer"&gt;https://nodejs.org/api/repl.html#repl_design_and_features&lt;/a&gt;. That's quite handy - but falls short of a full-featured interactive shell that has access to all necessary packages.&lt;/p&gt;

&lt;h1&gt;
  
  
  The solution
&lt;/h1&gt;

&lt;p&gt;Luckily, repl is available in node as a package ( &lt;a href="https://nodejs.org/api/repl.html#repl_repl" rel="noopener noreferrer"&gt;https://nodejs.org/api/repl.html#repl_repl&lt;/a&gt; ) - so all that's necessary is to write a small script that starts a repl instance and pulls in everything you need.&lt;/p&gt;

&lt;p&gt;You'll need to inject all the packages you want to use interactively into the repl console via a launcher script. It's also handy to configure repl in the script, and I show some examples below:&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;/*
  Opinionated example on how
  to make the repl console environment aware.
*/&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="cm"&gt;/*
  If you intend to launch the console
  across multiple environments (development/production/staging) -
  it's helpful print the environment
  to avoid unfortunate mistakes.
*/&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Starting console - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;repl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;repl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;util&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;util&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startConsole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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="cm"&gt;/*
    The lines below configure output formatting for repl.

    W/o specifying any output options, you'd get
    formatting like
    &amp;gt; a = {a: {b: {c: {d: {e: {f: {g: {h: 1}}}}}}}}
    { a: { b: { c: [Object] } } }

    With these options, you'd get
    &amp;gt; a = {a: {b: {c: {d: {e: {f: {g: {h: 1}}}}}}}}
    { a: { b: { c: { d: { e: { f: { g: { h: 1 } } } } } } } }

    Note these options are the same as the options passed to inspect
    https://nodejs.org/api/util.html#util_util_inspect_object_options
  */&lt;/span&gt;
  &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
  &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="cm"&gt;/*
    repl is supposed to use util.inspect to format by default.
    However, w/o explicitly specifying {writer: util.inspect},
    I was not able to get the options above to be successfully applied
    for eval results formatting. They _do_ get applied to
    console.log formatting, though, in either case.

    You may want to specify other options - see
    https://nodejs.org/api/repl.html#repl_repl_start_options
    for a comprehensive list - e.g. {prompt: "xyz&amp;gt;"} is a handy one.
  */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;replServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;repl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="cm"&gt;/*
    Pull in any number of modules here - these are the
    modules that will be available to you in the repl instance.
  */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;util&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;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;moduleName&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;replServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;moduleName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moduleName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="cm"&gt;/*
    This is not necessary in newer versions of node,
    but in older versions I wasn't able to pull in
    ad-hoc modules to a running repl instance w/o it.
  */&lt;/span&gt;
  &lt;span class="nx"&gt;replServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;require&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="nf"&gt;startConsole&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The way I personally set it up is by having all the things my application cares about available as a single module defined in my application - including both npm packages and my own library/reusable code.&lt;/p&gt;

&lt;p&gt;I use this single module in application code, scripts, background jobs, and also in the repl console - that way accessing functionality looks the same in all contexts, and I can easily memorize commands and have them at my fingertips.&lt;/p&gt;

&lt;p&gt;My script ends up looking more 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="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Starting console - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;repl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;repl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;util&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;util&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="cm"&gt;/*
  This contains all the modules I want to pull in
*/&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../lib.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startConsole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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="cm"&gt;/*
    E.g. establish connections to various databases...
  */&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
  &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;replServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;repl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;replServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;startConsole&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Starting the console
&lt;/h1&gt;

&lt;p&gt;I usually start the script through npm/yarn, via package.json:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scripts&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="p"&gt;...&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;console&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;node --experimental-repl-await ./scripts/console.js&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="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I love &lt;code&gt;--experimental-repl-await&lt;/code&gt; (&lt;a href="https://nodejs.org/api/cli.html#cli_experimental_repl_await" rel="noopener noreferrer"&gt;https://nodejs.org/api/cli.html#cli_experimental_repl_await&lt;/a&gt; - added in Node.js 10.0.0), and I hope it makes its way out of experimental soon. It allows &lt;code&gt;await&lt;/code&gt;ing on async commands in the repl console. Without it, working with promises is quite annoying.&lt;/p&gt;

&lt;p&gt;After that's in, it's just &lt;code&gt;yarn run console&lt;/code&gt; or &lt;code&gt;npm run console&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Working with the console
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;yarn&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;Hello&lt;/span&gt; &lt;span class="nx"&gt;world&lt;/span&gt;
&lt;span class="kc"&gt;undefined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note how &lt;code&gt;console.log("...")&lt;/code&gt; produces 2 lines as output. It performs its side effect of printing and returns a value - and repl will print the result of each expression it evaluates. For example, variable declarations return undefined, but variable assignments return the assigned value:&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="kc"&gt;undefined&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's handy to know if you want to skip printing the output of some expression.&lt;/p&gt;

&lt;p&gt;In most cases, I tend to avoid using variable declarations in repl, since you can assign a variable without declaring it. The reason is that I often copy-paste sections of code from a text editor, and variable declarations are not re-runnable. In application code I'll usually use &lt;code&gt;const&lt;/code&gt;, but in repl that locks you out from fixing mistakes, especially with e.g. function declarations.&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="kc"&gt;undefined&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nx"&gt;Uncaught&lt;/span&gt; &lt;span class="nx"&gt;SyntaxError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Identifier&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="nx"&gt;already&lt;/span&gt; &lt;span class="nx"&gt;been&lt;/span&gt; &lt;span class="nx"&gt;declared&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Persistent history
&lt;/h1&gt;

&lt;p&gt;Repl supports bi-directional reverse-i-search similar to zsh. I.e. you can search back through your history by pressing &lt;code&gt;ctrl+r&lt;/code&gt; (or &lt;code&gt;ctrl+s&lt;/code&gt; to search forward) - which makes preserving history between runs potentially very worth it.&lt;/p&gt;

&lt;p&gt;History is preserved in a file, so you'll need to choose where to store it. I store it in a &lt;code&gt;.gitignore&lt;/code&gt;d folder in my project. E.g. the default node.js repl console stores history by default, in your home folder in &lt;code&gt;.node_repl_history&lt;/code&gt; ( &lt;a href="https://nodejs.org/api/repl.html#repl_persistent_history" rel="noopener noreferrer"&gt;https://nodejs.org/api/repl.html#repl_persistent_history&lt;/a&gt; ).&lt;/p&gt;

&lt;p&gt;Here's the code for enabling persistent command history - the path is relative to the root of the project ( &lt;a href="https://nodejs.org/api/repl.html#repl_replserver_setuphistory_historypath_callback" rel="noopener noreferrer"&gt;https://nodejs.org/api/repl.html#repl_replserver_setuphistory_historypath_callback&lt;/a&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="nx"&gt;replServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setupHistory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./no_commit/repl_history&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Loaded history!&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I add this at the end of the &lt;code&gt;startConsole()&lt;/code&gt; function above, adding the environment as the filename suffix:&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="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Starting console - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;repl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;repl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;util&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;util&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startConsole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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;await&lt;/span&gt; &lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
  &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;replServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;repl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;replServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&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;historyPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`./no_commit/repl_history_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="nx"&gt;replServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setupHistory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;historyPath&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="nf"&gt;startConsole&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;It's quite easy to set up an interactive Javascript shell based on Node's REPL module. It can be configured flexibly, have access to application logic, and any installed npm modules.&lt;/p&gt;

&lt;p&gt;Unlike a Chrome console, it can be used to run arbitrary commands on your computer (or a remote computer), and not just for working with a particular application.&lt;/p&gt;

</description>
      <category>node</category>
      <category>tooling</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Quick manual deploy to EC2 with github</title>
      <dc:creator>Alexey</dc:creator>
      <pubDate>Fri, 12 Mar 2021 03:54:52 +0000</pubDate>
      <link>https://dev.to/alexeydc/quick-manual-deploy-to-ec2-with-github-240l</link>
      <guid>https://dev.to/alexeydc/quick-manual-deploy-to-ec2-with-github-240l</guid>
      <description>&lt;p&gt;This article is about my preferred way to bootstrap a deploy process ASAP (with AWS/EC2). It is most useful for prototyping ideas or publishing solo projects. For more mature projects, I've used a variety of other deploys styles - which are all more expensive to set up.&lt;/p&gt;

&lt;p&gt;I use a simple on-demand instance in EC2 with SSH, and use &lt;code&gt;git clone&lt;/code&gt;/&lt;code&gt;git pull&lt;/code&gt; via deploy keys - then directly running the server the same way I work with it locally (except with a different set of environment variables).&lt;/p&gt;

&lt;p&gt;I pair this with an nginx/SSL configuration with free SSL certificates via certbot: &lt;a href="https://dev.to/alexeydc/modern-https-configuration-2h86"&gt;https://dev.to/alexeydc/modern-https-configuration-2h86&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Deploy keys
&lt;/h1&gt;

&lt;p&gt;Github has a notion of deploy keys, designed for this very purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Generate keys
&lt;/h2&gt;

&lt;p&gt;On the machine you want to deploy to, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh-keygen -t ed25519 -f ~/.ssh/my_project_deploy_key -C "your_email@example.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;-t ed25519&lt;/code&gt; - an upgrade from RSA to elliptic curve cryptography. More info: &lt;a href="https://medium.com/risan/upgrade-your-ssh-key-to-ed25519-c6e8d60d3c54" rel="noopener noreferrer"&gt;https://medium.com/risan/upgrade-your-ssh-key-to-ed25519-c6e8d60d3c54&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this case, we're only going to use the key to pull the repo on a deploy - so you don't really need the &lt;code&gt;-C&lt;/code&gt; flag. But e.g. if you develop on a remote machine and plan to commit with the key - it's helpful to tie it to your github email.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Add the public key to github
&lt;/h2&gt;

&lt;p&gt;In the target repository, go to &lt;em&gt;Settings&lt;/em&gt; -&amp;gt; &lt;em&gt;Deploy keys&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Click &lt;code&gt;Add Deploy Key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Give it a descriptive title, like "Production MyProject EC2". Copy-paste the contents of &lt;code&gt;~/.ssh/my_project_deploy_key.pub&lt;/code&gt; into the key field, &lt;em&gt;don't&lt;/em&gt; tick &lt;code&gt;Allow write access&lt;/code&gt; (or do - but for deploys you shouldn't want to).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Configure git to use the key
&lt;/h2&gt;

&lt;p&gt;Actually, if we had named the key &lt;code&gt;id_rsa&lt;/code&gt;, git would already have access to it (just make sure to clone via the &lt;code&gt;git:&lt;/code&gt; URL, not &lt;code&gt;https:&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;However, then you are limited to just one repository for this machine, and... are forced to use the default key name.&lt;/p&gt;

&lt;p&gt;Instead of being handcuffed by defaults, let's set up the config ( &lt;a href="https://snipe.net/2013/04/11/multiple-github-deploy-keys-single-server/" rel="noopener noreferrer"&gt;https://snipe.net/2013/04/11/multiple-github-deploy-keys-single-server/&lt;/a&gt; ):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vim ~/.ssh/config

# Then add this type of config to the file
Host github.com-any_string_identifying_our_service
HostName github.com
IdentityFile /home/ubuntu/.ssh/my_project_deploy_key
User git

# You can have entries for multiple repos
Host github.com-another_string_for_another_project
HostName github.com
IdentityFile /home/ubuntu/.ssh/other_deploy_key
User git

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

&lt;/div&gt;



&lt;p&gt;Then you need a special clone command, using the host defined above. Let's say our github organization name is &lt;code&gt;shiny_deploys_inc&lt;/code&gt;, and the repository name is &lt;code&gt;deploy_alpha&lt;/code&gt;. The web URL for the repo would be &lt;code&gt;https://github.com/shiny_deploys_inc/deploy_alpha&lt;/code&gt;. Here is the clone command, given the example config above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone git@github.com-any_string_identifying_our_service:shiny_deploys_inc/deploy_alpha.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, &lt;code&gt;git pull&lt;/code&gt; should work as usual to update the project.&lt;/p&gt;

&lt;h1&gt;
  
  
  What next?
&lt;/h1&gt;

&lt;p&gt;This barebones setup can work quite well to get started, but it's far from being complete.&lt;/p&gt;

&lt;p&gt;Each deploy will involve a bit of downtime. One way to get around that is with pm2 - check out my article on setting up a rolling deploy with pm2 that helps mitigate downtime due to backend updates &lt;a href="https://dev.to/alexeydc/pm2-express-nextjs-with-github-source-zero-downtime-deploys-n71"&gt;https://dev.to/alexeydc/pm2-express-nextjs-with-github-source-zero-downtime-deploys-n71&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>github</category>
      <category>webdev</category>
      <category>deploy</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Modern HTTPS configuration</title>
      <dc:creator>Alexey</dc:creator>
      <pubDate>Wed, 10 Mar 2021 01:34:58 +0000</pubDate>
      <link>https://dev.to/alexeydc/modern-https-configuration-2h86</link>
      <guid>https://dev.to/alexeydc/modern-https-configuration-2h86</guid>
      <description>&lt;p&gt;I've tried many ways of setting up HTTPS servers, and I've finally found a favorite method.&lt;/p&gt;

&lt;p&gt;Instead of paying for production certificates, it's easy to verify your own certificates via cerbot &lt;a href="https://certbot.eff.org/"&gt;https://certbot.eff.org/&lt;/a&gt; and LetsEncrypt &lt;a href="https://letsencrypt.org/"&gt;https://letsencrypt.org/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The flow below is for Ubuntu, and involves using nginx to serve a file - as opposed to having the file served by your actual backend. I find this solution to be more elegant if you have full access to your server.&lt;/p&gt;

&lt;h1&gt;
  
  
  Installing cerbot and receiving a certificate
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Install certbot
&lt;/h2&gt;

&lt;p&gt;For ubuntu 20:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Earlier versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo add-apt-repository ppa:certbot/certbo
sudo apt-get update
sudo apt-get install certbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Run certbot
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo certbot certonly --manual
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will stop at a prompt; keep it open and follow the next steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Set up nginx to serve the right data for certbot
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Snap didn't have nginx when I was doing this setup, so:
sudo apt install nginx
sudo ufw allow 'Nginx HTTP'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(with reference to &lt;a href="https://docs.nginx.com/nginx/admin-guide/web-server/serving-static-content/"&gt;https://docs.nginx.com/nginx/admin-guide/web-server/serving-static-content/&lt;/a&gt; ):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# By default nginx will serve files from /var/www/html
# Put the cert there by default, or see what works best for your setup:
sudo mkdir /var/www/html/.well-known
sudo mkdir /var/www/html/.well-known/acme-challenge
sudo vim /var/www/html/.well-known/acme-challenge/&amp;lt;filename from certbot&amp;gt;
&amp;lt;copy in certbot data&amp;gt;
sudo chmod a=r /var/www/html/.well-known/acme-challenge/&amp;lt;filename from certbot&amp;gt;

# We don't need to change anything with the above folder structure.
# Alternatively, we can change the config
sudo vim /etc/nginx/sites-enabled/default
# If you do change the config, reload nginx
sudo systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Finalizing verification
&lt;/h2&gt;

&lt;p&gt;Go back to certbot; it should be prompting to hit Enter. Do that, and the verification should complete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/yourdomain.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/yourdomain.com/privkey.pem
   Your certificate will expire on 2021-06-07. To obtain a new or
   tweaked version of this certificate in the future, simply run
   certbot again. To non-interactively renew *all* of your
   certificates, run "certbot renew"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Using the certificate
&lt;/h1&gt;

&lt;p&gt;The newly created certificates will only be available to root &lt;a href="https://certbot.eff.org/docs/using.html#where-are-my-certificates"&gt;https://certbot.eff.org/docs/using.html#where-are-my-certificates&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo chmod 0755 /etc/letsencrypt/{live,archive}
# In the doc above, this isn't mentioned as necessary, but I couldn't get access to the privkey w/o being explicit
sudo chmod 0755 /etc/letsencrypt/live/yourdomain.com/privkey.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you can either choose to use these certificates directly by your service, or let nginx deal with that layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring nginx
&lt;/h2&gt;

&lt;p&gt;To make a server available on HTTPS w/o a port suffix, it's necessary to run on port 443. That requires elevated privileges in linux, and it's not a good idea to run Node.js that way - though nginx is perfectly suited just for this.&lt;/p&gt;

&lt;p&gt;A good way to set up port-less access is to configure port forwarding via nginx: from 443 to e.g. 8080 - you can connect from nginx to your service directly via HTTP w/o SSL.&lt;/p&gt;

&lt;p&gt;I usually create a file in &lt;code&gt;sites-available&lt;/code&gt;, then symlink it inside of &lt;code&gt;sites-enabled&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/nginx/sites-available/yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's say you're running your application server on port 8080 (you can use HTTP with your application, since nginx will handle the HTTPS handshake/encryption):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt; &lt;span class="s"&gt;http2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;yourdomain.com&lt;/span&gt; &lt;span class="s"&gt;www.yourdomain.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/yourdomain.com/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# managed by Certbot&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/yourdomain.com/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# managed by Certbot&lt;/span&gt;

  &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;ssl_config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;access_log&lt;/span&gt;            &lt;span class="n"&gt;/var/log/nginx/yourdomain.access.log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt;        &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt;        &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt;        &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt;        &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Fix the "It appears that your reverse proxy set up is broken" error.&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_pass&lt;/span&gt;          &lt;span class="s"&gt;http://localhost:8080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_read_timeout&lt;/span&gt;  &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;"Upgrade"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&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;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;yourdomain.com)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# managed by Certbot&lt;/span&gt;


  &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;yourdomain.com&lt;/span&gt; &lt;span class="s"&gt;www.yourdomain.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://yourdomain.com&lt;/span&gt;&lt;span class="nv"&gt;$request_uri&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;Don't forget to replace &lt;code&gt;yourdomain.com&lt;/code&gt; with your actual domain.&lt;/p&gt;

&lt;p&gt;After the config is in &lt;code&gt;sites-available&lt;/code&gt;, it should be symlinked into &lt;code&gt;sites-enabled&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/your-domain.com  /etc/nginx/sites-enabled/your-domain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you just have 1 site, you could use &lt;code&gt;sudo vim /etc/nginx/sites-available/default&lt;/code&gt; - just append the config above to that file.&lt;/p&gt;

&lt;p&gt;Once the config is set up,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  HTTPS in local development
&lt;/h1&gt;

&lt;p&gt;Usually, you can rely on HTTP in local development - since browsers special-case behavior for &lt;code&gt;http://localhost&lt;/code&gt;, and don't require the same degree of security as with remote hosts.&lt;/p&gt;

&lt;p&gt;There are &lt;a href="https://web.dev/when-to-use-local-https/"&gt;some rare situations&lt;/a&gt; where HTTPS locally is useful - e.g.&lt;br&gt;
some OAuth providers that you may need to redirect to your site from may disallow &lt;code&gt;http&lt;/code&gt; redirect URLS. &lt;/p&gt;

&lt;p&gt;In those cases, mkcert can be very handy ( &lt;a href="https://github.com/FiloSottile/mkcert"&gt;https://github.com/FiloSottile/mkcert&lt;/a&gt; ):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Only do this once for all mkcert projects
brew install mkcert
brew install nss # for Firefox
mkcert -install
# Set up this repo with mkcert certificates
# I personally just keep my mkcert right in the folder of the repo.
# Don't forget to add the directory to .gitignore!
mkdir mkcert
cd mkcert
mkcert localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that you'll need to get your application server to load those certificates and set up HTTPS - I'll leave that out of scope for this article.&lt;/p&gt;

&lt;h1&gt;
  
  
  Maintenance
&lt;/h1&gt;

&lt;p&gt;The issued certificates are good for 3 months. To get the time remaining, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo certbot certificates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To re-issue fresh certificates manually, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo certbot --force-renewal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will renew all certificates from certbot (i.e. it's intended to support multiple certificates and services on one machine).&lt;/p&gt;

&lt;p&gt;Certbot is built to be automated - so choose your own style, set up a crontab if you like. The general-purpose renew command is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo certbot renew
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more information, see &lt;a href="https://certbot.eff.org/docs/using.html?highlight=renew#renewing-certificates"&gt;https://certbot.eff.org/docs/using.html?highlight=renew#renewing-certificates&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>tutorial</category>
      <category>node</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
