<?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>Forem: Jake Marsh</title>
    <description>The latest articles on Forem by Jake Marsh (@jakemmarsh).</description>
    <link>https://forem.com/jakemmarsh</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%2F232201%2F04fb9977-c38d-457a-bcb5-5ae13d4a5396.jpeg</url>
      <title>Forem: Jake Marsh</title>
      <link>https://forem.com/jakemmarsh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jakemmarsh"/>
    <language>en</language>
    <item>
      <title>Prompting Users to Reload Your Next.js App After an Update</title>
      <dc:creator>Jake Marsh</dc:creator>
      <pubDate>Mon, 05 Apr 2021 16:37:25 +0000</pubDate>
      <link>https://forem.com/walrusai/prompting-users-to-reload-your-next-js-app-after-an-update-2jpf</link>
      <guid>https://forem.com/walrusai/prompting-users-to-reload-your-next-js-app-after-an-update-2jpf</guid>
      <description>&lt;p&gt;The pages of a &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; application are served in one of two ways: server-side rendering, or client-side rendering. It's important to understand the distinction, and when each scenario occurs. (There is also &lt;a href="https://nextjs.org/docs/advanced-features/automatic-static-optimization" rel="noopener noreferrer"&gt;static generation&lt;/a&gt;, but we will disregard that for this walkthrough.)&lt;/p&gt;

&lt;p&gt;Server-side rendering is when the underlying Node.js server is handling the request, loading the corresponding page component (and any data dependencies), and returning the populated HTML that results. A page will be server-side rendered if it's the initial request to load the page, and the page implements either &lt;a href="https://nextjs.org/docs/api-reference/data-fetching/getInitialProps" rel="noopener noreferrer"&gt;&lt;code&gt;getInitialProps&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering" rel="noopener noreferrer"&gt;&lt;code&gt;getServerSideProps&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Client-side rendering is when the Javascript in the browser has taken over the handling of the request, and React will handle rendering the new page component and reconciling any differences. Client-side rendering occurs when the user has already loaded your application, and is navigating via the Next.js router (either &lt;a href="https://nextjs.org/docs/api-reference/next/router#userouter" rel="noopener noreferrer"&gt;directly&lt;/a&gt;) or via the &lt;a href="https://nextjs.org/docs/api-reference/next/link" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;lt;Link /&amp;gt;&lt;/code&gt;&lt;/a&gt; component.&lt;/p&gt;

&lt;p&gt;The important  caveat with client-side rendering is that once the user has loaded the application and each of the pages, requests are no longer being made to the server to render any of them -- the client is handling it all. This means that if you deploy a new version of your application while someone is using it, they could continue seeing and using the previous version of your application until they happen to reload.&lt;/p&gt;

&lt;p&gt;This can cause issues if you're making breaking changes, or fixing bugs, or making any other changes you'd prefer your users see ASAP. This risk is multiplied by the number of people using your application. So how can you handle new deploys on the client to ensure our users get the latest version?&lt;/p&gt;

&lt;p&gt;Next.js allows &lt;a href="https://nextjs.org/docs/api-reference/next.config.js/custom-webpack-config" rel="noopener noreferrer"&gt;customizing the Webpack configuration&lt;/a&gt; used at build time via a &lt;code&gt;next.config.js&lt;/code&gt; file. It will automatically pass in various relevant arguments; the one that we're interested in is &lt;code&gt;buildId&lt;/code&gt;. By default, this is a random string unique to each build.&lt;/p&gt;

&lt;p&gt;Combined with Webpack's &lt;a href="https://webpack.js.org/plugins/define-plugin/" rel="noopener noreferrer"&gt;&lt;code&gt;DefinePlugin&lt;/code&gt;&lt;/a&gt;, you can expose this buildId to our application by replacing any checks for &lt;code&gt;process.env.BUILD_ID&lt;/code&gt; with the real &lt;code&gt;buildId&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// next.config.js&lt;/span&gt;

&lt;span class="kr"&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="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="nf"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;buildId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DefinePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process.env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;BUILD_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buildId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&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;This means that the resulting bundles served on the client will have the real &lt;code&gt;buildId&lt;/code&gt; available to them when checking &lt;code&gt;process.env.BUILD_ID&lt;/code&gt;. Since these bundles stay loaded as client-side navigation occurs, this will remain a static reference to the &lt;code&gt;buildId&lt;/code&gt; loaded on the client.&lt;/p&gt;

&lt;p&gt;Next, you'll want to also expose this &lt;code&gt;process.env.BUILD_ID&lt;/code&gt; variable in our server-side environment. This is because when you deploy a new version of your application, anything being handled by the server will immediately be operating on the newest version. You can do this via Next.js's &lt;a href="https://nextjs.org/docs/api-routes/introduction" rel="noopener noreferrer"&gt;API routes&lt;/a&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// pages/api/build-id.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default &lt;/span&gt;&lt;span class="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;NextApiRequest&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;NextApiResponse&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&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;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;buildId&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;BUILD_ID&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;With this new endpoint exposing &lt;code&gt;process.env.BUILD_ID&lt;/code&gt; from the server, you have a route we can hit at any time to get the &lt;em&gt;newest&lt;/em&gt; deployed buildId: &lt;code&gt;/api/build-id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since the client will now have a static reference to its own &lt;code&gt;buildId&lt;/code&gt;, and the server now has the endpoint always returning the newest &lt;code&gt;buildId&lt;/code&gt;, we can implement our own polling and diffing to determine if the user needs to reload. Below is a component that encapsulates this logic, polling for the latest &lt;code&gt;buildId&lt;/code&gt; every 30 seconds via a &lt;code&gt;useInterval&lt;/code&gt; hook. This can then be rendered anywhere in your application.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;superagent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useInterval&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Function&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;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lead&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&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;savedCallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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;savedCallback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;callback&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;callback&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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;tick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;savedCallback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;lead&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;tick&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;interval&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&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;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;interval&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DeployRefreshManager&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;JSX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Element&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useInterval&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;buildId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;request&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;/api/build-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;body&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;buildId&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;BUILD_ID&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;buildId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BUILD_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// There's a new version deployed that we need to load&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;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&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="kc"&gt;null&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;At walrus.ai, we display a non-closable modal to the user with just one possible action: reloading the page. Clicking this button simply  calls &lt;code&gt;window.location.reload()&lt;/code&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%2Fgaaf3cemr5j748frb42x.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%2Fgaaf3cemr5j748frb42x.png" alt="Screenshot of walrus.ai's new version modal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is achieved by simply setting a boolean state value in the above if statement, and conditionally returning your modal element from the component instead of always returning null.&lt;/p&gt;

&lt;p&gt;You'll now be able to rely on the fact that your users are always using the latest version of your application, or at least being prevented from taking actions until they've reloaded.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Writing Resilient End-to-End Tests</title>
      <dc:creator>Jake Marsh</dc:creator>
      <pubDate>Mon, 20 Apr 2020 18:38:03 +0000</pubDate>
      <link>https://forem.com/walrusai/writing-resilient-end-to-end-tests-mn</link>
      <guid>https://forem.com/walrusai/writing-resilient-end-to-end-tests-mn</guid>
      <description>&lt;h2&gt;
  
  
  What is end-to-end testing?
&lt;/h2&gt;

&lt;p&gt;End-to-end testing is the process of testing your application through replicating the expected full flows and behaviors of an end user. For example, if you wanted to test that an application's signup flow was working, you would use code to automatically navigate and complete the signup flow in the browser just like a user.&lt;/p&gt;

&lt;p&gt;By testing the full functionality of your application in this manner, you're ensuring everything is working as expected from the client, to the API, to the database. It eliminates the seams that occur when unit testing each parts of your application in isolation.&lt;/p&gt;

&lt;p&gt;If it's possible to gain more confidence with end-to-end tests, why don't people do more of it? &lt;strong&gt;Because it's hard&lt;/strong&gt;. One of the biggest issues is resiliency: what happens if you change the text of a button that your test was looking for? What happens if you hit a network spike and something takes longer than expected? Without handling cases like these, your end-to-end tests are likely to start failing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is resiliency important?
&lt;/h2&gt;

&lt;p&gt;In a perfect world, end-to-end tests should be run any time a change is made to your application. This is because the smallest (perceived) change is always capable of causing unforeseen issues throughout the rest of your application. To achieve this, teams will typically execute their end-to-end tests within their CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;If your end-to-end tests are running inside your CI/CD pipeline, that means they're directly intertwined with your deployment process. Any delays or failures will impede (or prevent) the deployment from completing. Change the button text in your signup flow from "Next" to "Continue"? If your tests weren't updated for that change, you're about to hit a deployment failure and lose up to 30 minutes of precious time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a flake, and what causes them?
&lt;/h2&gt;

&lt;p&gt;A "flake" is a (usually) non-deterministic failure caused by something that is either intermittent or unexpected. Due to the nature of end-to-end tests and the many layers involved, a flake can be caused by any number of things. Some of the more common examples are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Asynchronous delays.&lt;/strong&gt; If submitting a form on your page suddenly takes 5 seconds instead of 2, the page will not be behaving as your tests may expect. Spinners may stick around, buttons may not re-enable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identifier changes.&lt;/strong&gt; As we've mentioned, changing something like the text of a button could cause failures. That's also true of non-user-facing identifiers such as HTML classes or IDs, which can change at any time due to unrelated engineering updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actual application/UX changes.&lt;/strong&gt; In the case that your team has actually carried out a large redesign or refactor of your application, your existing tests are very likely to start failing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Flakes and How to Avoid Them
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Asynchronous Delays
&lt;/h2&gt;

&lt;p&gt;Asynchronous delays, often resulting from network requests, are the largest and broadest category of test flakes. This is because asynchronous requests are happening quite often in an average web application, affecting many other parts of the app.&lt;/p&gt;

&lt;p&gt;Submitting the login form? Waiting for a response from the API. Trying to view a list of data? Waiting for data to be fetched. Just clicked a button? Waiting for the request to finish before updating the UI. You may even have to wait on something like an email being sent and arriving in your test inbox.&lt;/p&gt;

&lt;p&gt;These are all examples of things that most end-to-end tests will be doing often: navigating, filling out forms, clicking elements, waiting for things to happen. And they're all unpredictable!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What can you do?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;There are a few things you can do in your end-to-end tests to better handle any form of asynchronous delay. None of these are foolproof, but they should get most of the way to resiliency around these issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Explicitly wait on network requests when possible.&lt;/strong&gt; Many testing libraries provide APIs to hook into the loading status of the browser page. This can be done when you're submitting a form that you know will result in a redirect, or when you're clicking a link. In Puppeteer, for example, they provide &lt;code&gt;page.waitForNavigation()&lt;/code&gt;. Clicking a button to submit a form, then, might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
   &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForNavigation&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
   &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button[type="submit"]&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;&lt;strong&gt;2. Wait often to allow for arbitrary load times.&lt;/strong&gt; Even when using a helper like &lt;code&gt;waitForNavigation&lt;/code&gt;, there may be additional delay involved post-request. For example, the application may take a second or two to enable the button you'll be clicking next. In instances like these, it can be helpful to have a simple &lt;code&gt;wait&lt;/code&gt; helper to wait a small amount of hardcoded time before proceeding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Helper&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&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;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;  

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForNavigation&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button[type="submit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.some-later-rendered-element&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;&lt;strong&gt;3. Use polling.&lt;/strong&gt; Many testing libraries will provide a method (or its easy to write your own), that "polls" for an element on the screen. This makes your test more resilient to those millisecond differences when finding an element to operate on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bad&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Good&lt;/span&gt;
&lt;span class="k"&gt;await&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you're testing some process that can be particularly long and/or unpredictable such as querying a large set of data for a new entry, or receiving an email, it can be helpful to carry out your logic and assertions in a custom polling method. This means you're checking on some defined interval for the assertion (i.e. "my data entry did appear"), with a hardcoded timeout that determines a "failure".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pollUntilTimeout&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;return&lt;/span&gt; &lt;span class="o"&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;page&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.some-new-element&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="na"&gt;intervalMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;timeoutMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New element not found.&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;h2&gt;
  
  
  Identifier Changes
&lt;/h2&gt;

&lt;p&gt;Most identifiers or attributes used in testing exist for other purposes. Classes, for example, are primarily used for styling. Element tags are used for DOM layout and specific to element purpose. Any time one of these identifiers is changed for their real purpose (updating a button's styling, changing a button to a link), your tests can break.&lt;/p&gt;

&lt;p&gt;One option is to define and enforce strict rules around naming behaviors and update processes for element identifiers. For example, you could add a new class to every element you'll be using in your test, prefixed with &lt;code&gt;test-&lt;/code&gt;, and ensure those are always kept in sync. However, this can be a painful process to remember and enforce.&lt;/p&gt;

&lt;p&gt;Ideally, you can write your tests to be better prepared for changes to identifiers. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What can you do?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Avoid relying on "internals"&lt;/strong&gt;. Things like &lt;code&gt;class&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt; should generally be avoided and assumed to be unstable, as they may change at any time due to internal engineering efforts. Of course there may still be scenarios in which you have no other option, but be prepared for changes to affect your tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bad&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input.form--input.some-styling.text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Good&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input[type='text'&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;&lt;strong&gt;2. Use selectors based on actual functionality to the end-user.&lt;/strong&gt; Since the goal of your end-to-end test is to ensure that an end-user is still able to complete some flow, it makes sense that it should be written in the same way a user may look for something. A user of your application isn't going to be looking for the selector &lt;code&gt;button.btn.btn--green&lt;/code&gt; when they want to create a new post, they'll look for a button that contains the words "Create Post".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bad&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button.btn.btn--green&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Good&lt;/span&gt;
&lt;span class="k"&gt;await&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForXPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;//button[contains(., 'Create Post')]&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;&lt;strong&gt;3. Be (reasonably) lenient with your matchers.&lt;/strong&gt; In the example above, we're searching for a button with the text "Create Post". But what if a change is made and it now says "Create New Post"? Barely anything has changed, but your test is going to break. If there's no other conflicts on the page, it can be helpful to loosen your matcher. What if we just search for "Create"?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bad&lt;/span&gt;
&lt;span class="k"&gt;await&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForXPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;//button[contains(., 'Create Post')]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// Good&lt;/span&gt;
&lt;span class="k"&gt;await&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForXPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;//button[contains(., 'Create')]&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;h2&gt;
  
  
  Real Application/UX Changes
&lt;/h2&gt;

&lt;p&gt;The last major cause of end-to-end test failures are larger instances of what we've already discussed: intentional changes to your application and/or UX. If your signup flow, for example, is completely redesigned from the ground up, your test logic and matchers will likely no longer be correct.&lt;/p&gt;

&lt;p&gt;Although this class of changes is least common, they are by far the most expensive in terms of time to update the applicable end-to-end tests. There's not much you can do in your tests to prepare for large sweeping changes, due to the need to balance non-colliding and specific matchers with the possibility of an entirely different page.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What can you do?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="http://walrus.ai"&gt;walrus.ai&lt;/a&gt; is a new solution for end-to-end testing with ease. All you provide is a user story written in plain English instructions, and the test is then interpreted and executed by a human and automated for all future runs. Any changes or flakes are handled, and so your tests will only fail when there's a &lt;strong&gt;true&lt;/strong&gt; failure.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>automation</category>
      <category>qa</category>
    </item>
    <item>
      <title>An Introduction to End-to-End Testing</title>
      <dc:creator>Jake Marsh</dc:creator>
      <pubDate>Mon, 11 Nov 2019 19:04:30 +0000</pubDate>
      <link>https://forem.com/jakemmarsh/an-introduction-to-end-to-end-testing-p5n</link>
      <guid>https://forem.com/jakemmarsh/an-introduction-to-end-to-end-testing-p5n</guid>
      <description>&lt;h1&gt;
  
  
  What are end-to-end tests?
&lt;/h1&gt;

&lt;p&gt;You can think of automated tests as existing on a spectrum. Although they're &lt;a href="https://walrus.ai/blog/2019/11/unexpected-benefits-writing-tests/"&gt;all valuable&lt;/a&gt;, it's helpful to know the difference between the various types and when to use them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9oFXztVX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/awpnvsctxdtkp2totwa1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9oFXztVX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/awpnvsctxdtkp2totwa1.png" alt="The spectrum of different types of tests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At one end are unit tests, used to test the functionality of individual methods or components. These are helpful for ensuring that the core building blocks of your applications function as expected in isolation.&lt;/p&gt;

&lt;p&gt;In the middle are integration tests. These are tests abstracted up a level from unit tests, executing and asserting on code paths that traverse between multiple units or modules. Now we're starting to ensure the different pieces of our application interact with each other as expected.&lt;/p&gt;

&lt;p&gt;Lastly, on the opposite end, are end-to-end (E2E) tests. These are tests aimed to cover the entire surface area of your application from top to bottom. These should generally follow the code paths expected from your end-users to ensure they're as close to reality as possible.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why should you write end-to-end tests?
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iNW524J8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/lg3vafbnzhoz5gh3qrkf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iNW524J8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/lg3vafbnzhoz5gh3qrkf.png" alt="A pyramid representing the proportion of types of tests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the typical "testing pyramid" (above), E2E tests represent the very top of the pyramid. This is due to the fact that they can be expensive and slow both to write and maintain, and so should make up a smaller percentage of the tests your team is responsible for. What people typically fail to mention around this pyramid, however, is that as you move up the tiers of the pyramid each form of testing is providing you with more confidence.&lt;/p&gt;

&lt;p&gt;Why exactly do end-to-end tests provide more confidence? As mentioned before, they should emulate a real user as closely as possible. This means that when one of your E2E tests results in a real failure, you're catching a bug that a real user would have found on their own later. This is much more helpful than a unit test telling me my individual method returns the correct string, or my integration test telling me that my endpoint correctly invokes my method.&lt;/p&gt;

&lt;p&gt;And so why, then, is E2E the smallest tier of the testing pyramid? As we touched on above, E2E tests have historically been slow, painful, and expensive for a team to maintain. Frameworks were hard to set up and use, tests were flaky for a &lt;a href="https://walrus.ai/blog/2019/10/five-common-bugs/"&gt;variety of common reasons&lt;/a&gt;, and often times companies even had manual QA teams to fill this role.&lt;/p&gt;

&lt;p&gt;Luckily, things have changed and end-to-end tests are more feasible than ever before.&lt;/p&gt;

&lt;h1&gt;
  
  
  How do you begin writing end-to-end tests?
&lt;/h1&gt;

&lt;p&gt;Now that we understand &lt;em&gt;what&lt;/em&gt; E2E tests are and why they're important, how do we begin to implement them? Let's go over some popular options for web applications today.&lt;/p&gt;

&lt;p&gt;For the examples below, let's assume we're testing Amazon's "Add to Cart" functionality. We're going to search for "shoes", add a result to our cart, and simply ensure it's actually in the cart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Puppeteer (+ Jest)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/GoogleChrome/puppeteer"&gt;Puppeteer&lt;/a&gt; is a Node library by Google for running a "headless" instance of Chrome. Longstanding and widely known, it's been a large presence in the testing community for some time now. It allows you to programmatically interface with and manipulate Chrome (or Chromium).&lt;/p&gt;

&lt;p&gt;Since Puppeteer itself is not intended solely for testing purposes, it must be combined with an existing testing library in order to test and assert as we'd &lt;em&gt;expect&lt;/em&gt; (pun intended). &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt; is the most popular Javascript option, and can be paired with Puppeteer using &lt;a href="https://github.com/smooth-code/jest-puppeteer"&gt;jest-puppeteer&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;amazon.com&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;beforeAll&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;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://amazon.com&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;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should allow adding to cart&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input#twotabsearchtextbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;searchInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shoes&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;productLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#SEARCH_RESULTS-SEARCH_RESULTS a&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;productTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;productLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&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;productLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#add-to-cart-button&lt;/span&gt;&lt;span class="dl"&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#hlb-view-cart&lt;/span&gt;&lt;span class="dl"&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`document.querySelector(".sc-product-link").innerText.includes("&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;productTitle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;")`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Selenium (+ Jest)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.seleniumhq.org/projects/webdriver/"&gt;Selenium WebDriver&lt;/a&gt; is another API for automating and interfacing with the browser. As opposed to Puppeteer, Selenium has support for every popular browser.&lt;/p&gt;

&lt;p&gt;Like Puppeteer, Selenium itself does not provide testing or assertion functionality. However, we have another way to integrate with Jest: &lt;a href="https://github.com/alexeyraspopov/jest-webdriver/tree/master/packages/jest-environment-webdriver"&gt;jest-environment-webdriver&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;amazon.com&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;beforeAll&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;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://amazon.com&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;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should allow adding to cart&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input#twotabsearchtextbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="nx"&gt;searchInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendKeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shoes&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;productLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#SEARCH_RESULTS-SEARCH_RESULTS a&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;productName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;productLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getText&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nx"&gt;productLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#add-to-cart-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#hlb-view-cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findElements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.sc-product-link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))).&lt;/span&gt;&lt;span class="nx"&gt;resolves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toHaveText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  cypress.io
&lt;/h2&gt;

&lt;p&gt;Possibly the most popular option today, &lt;a href="http://cypress.io"&gt;cypress.io&lt;/a&gt; is a Javascript framework and SaaS offering for E2E testing your application. Through their framework and CLI, it's fairly easy to get started writing a simple Javascript test and seeing the results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;amazon.com&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;beforeAll&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;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://amazon.com&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;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should allow adding to cart&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&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;input#twotabsearchtextbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;searchInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shoes&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;productLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&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;#SEARCH_RESULTS-SEARCH_RESULTS a&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;productName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;productLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;productLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Add to Cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  walrus.ai
&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://walrus.ai"&gt;walrus.ai&lt;/a&gt; is a new, developer-first SaaS option looking to abstract away the complexities and pain points of true end-to-end testing. Through a combination of AI and human verification, walrus.ai allows developers to specify tests in plain English and with just a single API call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;walrus &lt;span class="nt"&gt;-a&lt;/span&gt; YOUR_API_TOKEN &lt;span class="nt"&gt;-u&lt;/span&gt; https://amazon.com &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s1"&gt;'Search for "shoes"'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    ‘Add a result to your shopping cart’ &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s1"&gt;'Ensure the shoes are in your cart'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Notice how many fewer instructions we need thanks to the use of human-readable sentences? Due to their higher-level description, they're also inherently less fragile than specific selectors.&lt;/p&gt;

&lt;p&gt;It's also free to get started with &lt;a href="http://walrus.ai"&gt;walrus.ai&lt;/a&gt; to see if it makes sense for you. &lt;a href="https://app.walrus.ai/login"&gt;Try it out today&lt;/a&gt;!&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping up
&lt;/h1&gt;

&lt;p&gt;If you didn't already, hopefully you now understand the difference between the various types of automated tests, as well as the benefits of writing end-to-end tests for your application. Maybe you've even used &lt;a href="http://walrus.ai"&gt;walrus.ai&lt;/a&gt; to get your first E2E test running ASAP!&lt;/p&gt;

&lt;p&gt;In a future post, we'll discuss best practices for ensuring your end-to-end tests are both resilient and robust.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>qa</category>
    </item>
    <item>
      <title>How to Choose and Track User Events to Build a Better Product</title>
      <dc:creator>Jake Marsh</dc:creator>
      <pubDate>Wed, 09 Oct 2019 16:12:13 +0000</pubDate>
      <link>https://forem.com/jakemmarsh/how-to-choose-and-track-user-events-to-build-a-better-product-1e5d</link>
      <guid>https://forem.com/jakemmarsh/how-to-choose-and-track-user-events-to-build-a-better-product-1e5d</guid>
      <description>&lt;p&gt;Online products are unique in the richness of user data they have available. Every action you take, whether hovering over an ad, clicking a link, or making a keystroke, can be tracked. This one concept has led to an entire industry of “big data”, valued most highly by ad companies. It’s for this reason that the practice has been in the headlines and at the top of minds recently.&lt;/p&gt;

&lt;p&gt;That being said, user activity is still a highly valuable source of information for applications not serving ads, but rather looking to improve their products for the sake of their users. This data is not only valuable for identifying user preferences, it is also key for understanding trends in user behavior. That’s why it’s important to still consider and implement event tracking when building a paid product.&lt;/p&gt;




&lt;p class="text--medium text-weight--heavy"&gt;
  Monolist is the command center for engineers — tasks, pull requests, messages, docs — all in one place. &lt;a href="/"&gt;Learn more&lt;/a&gt; or &lt;a href="https://app.monolist.co/login"&gt;try it for free&lt;/a&gt;.
&lt;/p&gt;



&lt;h2&gt;
  
  
  🎥 Tracking from the Get-Go
&lt;/h2&gt;

&lt;p&gt;There are three main advantages to tracking user events as soon as you’re able.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Set a precedent.&lt;/strong&gt; By tracking the right user events early, you’re working to set a precedent that this should be followed for all future user-facing features being shipped. This is a good practice to maintain to avoid event tracking becoming an afterthought or post-launch effort.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get the architecture in place.&lt;/strong&gt; Tackling event tracking early on and in a flexible manner means you’ll have the code and APIs in place for tracking events quickly, easily, and long into the future. We’ll go into more detail on this below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start learning ASAP.&lt;/strong&gt; This one should be obvious - the sooner you’re gathering the data, the sooner you can learn from it. If you’re tracking events from your first acquired user, you’re that much closer to improving the experience for your next user.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  🔑 Deciding What to Track
&lt;/h2&gt;

&lt;p&gt;There are two aspects to a user event that you track: the action the event represents (“user clicked on log in button”), and the data you choose to associate with that event (&lt;code&gt;{ user_id: 123 }&lt;/code&gt;).&lt;/p&gt;
&lt;h3&gt;
  
  
  What events should be tracked?
&lt;/h3&gt;

&lt;p&gt;It’s important to carefully consider what events get tracked and persisted. There are a few questions you should ask when determining if something is worth tracking.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Is this an action the user took directly?&lt;/strong&gt; For example, did they interact with an element or trigger an update? If the event occurs automatically or passively, it should likely not be tracked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does this action happen at a reasonable interval?&lt;/strong&gt; An example of an event that a user may trigger quite often would be a mouseover or hover event on an element. In this case, the event should probably not be tracked as it’ll introduce noise to your data, and it really won’t tell you much.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Could this event tell us something about user behavior?&lt;/strong&gt; The answer to this question is most often yes, but it’s still a good one to consider. If you’re tracking an irrelevant (or likely inevitable) event like “user pressed the ‘y’ key”, it may not be valuable to track.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What data should be included with an event?
&lt;/h3&gt;

&lt;p&gt;This is when it’s most important to find the balance between user privacy and data availability. When attaching a data payload to a user event, it’s important to minimize (or ideally eliminate) any personal or identifying user about the info. The data should be pared down to the bare minimum needed to infer your learnings about the user’s experience. This is because you’ll most likely end up persisting your user events in a third-party service.&lt;/p&gt;

&lt;p&gt;An example of a bad payload might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user_email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;“john.doe@gmail.com”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user_full_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;“John&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Doe”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email_sent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;“This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;I’m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;composing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;close&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;friend.”&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A good payload, on the other hand, might look something more like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user_gid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;“abc&lt;/span&gt;&lt;span class="mi"&gt;-123&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sent_email_gid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;“def&lt;/span&gt;&lt;span class="mi"&gt;-456&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Notice the difference? Rather than the raw data that is both identifying and personal, we only associate the event with the top-level (externalized) identifiers that we can use to then match to the proper entities internally.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠 Architecting Your Tracking Framework
&lt;/h2&gt;

&lt;p&gt;Now that we’ve discussed how to select what events you’re tracking, along with what data they entail, what’s the best way to incorporate this into our application?&lt;/p&gt;

&lt;h3&gt;
  
  
  Determine where the events take place
&lt;/h3&gt;

&lt;p&gt;Here at Monolist, we use &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt; for our UI and &lt;a href="https://redux.js.org/"&gt;Redux&lt;/a&gt; for our data management. Together, these give us two fairly well-defined places that an event can occur: inside the UI, i.e. a React component, or inside a Redux action, i.e. when making an API call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleButtonClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// We could track this event&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;closeActionItem&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="nx"&gt;dispatch&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;// We could also track this event&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In general, we prefer the latter approach: centralizing our tracking in our Redux actions. This gives us one place and one common approach for tracking an event, making it easy to find and understand. It also allows us to easily track events across platforms since we share our Redux code between our web and react-native clients. However, tracking within a component is still sometimes necessary when we want insight into lighter actions that don’t necessarily update the Redux store.&lt;/p&gt;

&lt;h3&gt;
  
  
  Determine where to send the events
&lt;/h3&gt;

&lt;p&gt;The actual event tracking method we call is also a Redux action, providing us a familiar method of invocation. This looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trackUserEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;eventName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;eventData&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;object&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="k"&gt;return&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/analytics/user-event&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;eventName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;eventData&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="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;As you can see, it’s fairly straight forward: if we’re in the production environment, send the event and its data back to our API. We send the data back to our API (rather than directly to a third-party service for three reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This allows the API to carry out any additional data scrubbing we may want to define.&lt;/li&gt;
&lt;li&gt;This allows us to carry out the third-party pushing in an async queue, ensuring the event gets persisted regardless of further UI interaction (user unloads page, etc.)&lt;/li&gt;
&lt;li&gt;We now have one point of third-party contact to modify (in the case of a service provider switch, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The examples we provided earlier end up looking like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleOpenClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onTrackUserEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="err"&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;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;closeActionItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dispatch&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;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;analyticsActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trackUserEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="err"&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;h3&gt;
  
  
  Determine where to put the data
&lt;/h3&gt;

&lt;p&gt;As we mentioned above, the primary reason for being strict about the data we persist for an event is that we may want to send our data to a third-party. There are many services dedicated to tracking your user events and helping you to analyze them (we use &lt;a href="https://mixpanel.com/"&gt;Mixpanel&lt;/a&gt; here at Monolist). These are great for being able to easily parse and visualize your data without additional engineering work. This also means your PM can even dive into the data themselves.&lt;/p&gt;

&lt;p&gt;Mixpanel has a relatively straight-forward API, which makes it easy for us to integrate from our Rails API. The endpoint that the above Redux action hits looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;track_user_event&lt;/span&gt;
    &lt;span class="no"&gt;PushUserEventWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see, it does just one thing: creates an async job to push the event to our third-party (Mixpanel). That worker then makes a quick API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PushUserEventWorker&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_event_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UserEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_event_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mixpanel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Mixpanel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Tracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"MIXPANEL_CLIENT_ID"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;event_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;event_data&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="n"&gt;mixpanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;event_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ip_address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This worker is now our single point of third-party contact if we ever decide to switch providers.&lt;/p&gt;

&lt;p&gt;An alternative to this approach would be to use something like Redshift to store your data, and then write in-house tooling to use the data however you see fit. Although this is a much larger undertaking, it’ll likely provide you more control over your data and analysis (and save you a few dollars).&lt;/p&gt;

&lt;h2&gt;
  
  
  📊 Analyzing the Data
&lt;/h2&gt;

&lt;p&gt;Once the data is stored, parsed, and visualized as we’ve described above, it’s up to you and your team to infer learnings from the data. Is that new signup flow seeing more engagement? Is the new feature you released last week resulting in any additional invites?&lt;/p&gt;

&lt;p&gt;How has your team used event tracking to learn and implement product changes? &lt;a href="https://twitter.com/monolistapp"&gt;Let us know on Twitter&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  ❗️ Are you a software engineer?
&lt;/h2&gt;

&lt;p&gt;At Monolist, we’re focused on building the global command center for software engineers. If you want to try it for free, just &lt;a href="https://app.monolist.co/login"&gt;click here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>tracking</category>
      <category>javascript</category>
    </item>
    <item>
      <title>TypeScript 3.7 Features in Production: Optional Chaining, Nullish Coalescing, and Assertion Functions</title>
      <dc:creator>Jake Marsh</dc:creator>
      <pubDate>Mon, 07 Oct 2019 17:39:50 +0000</pubDate>
      <link>https://forem.com/jakemmarsh/typescript-3-7-features-in-production-optional-chaining-nullish-coalescing-and-assertion-functions-3l9l</link>
      <guid>https://forem.com/jakemmarsh/typescript-3-7-features-in-production-optional-chaining-nullish-coalescing-and-assertion-functions-3l9l</guid>
      <description>&lt;p&gt;At &lt;a href="https://monolist.co"&gt;Monolist&lt;/a&gt;, we're building the command center for engineers. We integrate with all of the tools engineers use (code hosting, project management, alerting),&lt;br&gt;
and aggregate all of their tasks in one place. If you've read our &lt;a href="https://monolist.co/blog/2019/09/optimizing-for-iteration-tech-stack/"&gt;previous posts&lt;/a&gt;, you know we're big fans of TypeScript and highly recommend it.&lt;/p&gt;

&lt;p&gt;Microsoft has just announced the &lt;a href="https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-beta/"&gt;TypeScript 3.7 Beta&lt;/a&gt;, and it included multiple features we were excited to start using ASAP here at Monolist. We're going to be diving into some of these features and how we're already using them in production.&lt;/p&gt;
&lt;h2&gt;
  
  
  Optional Chaining
&lt;/h2&gt;

&lt;p&gt;Optional chaining is a new feature that allows you to chain property accesses without worrying about &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;undefined&lt;/code&gt;. If it encounters one of these values, it will stop executing the expression. This means you no longer have to chain &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; when accessing a nullable property.&lt;/p&gt;

&lt;p&gt;If you're familiar with Ruby (which &lt;a href="https://monolist.co/blog/2019/10/ruby-2-7/"&gt;we use for our API&lt;/a&gt;), this is similar to the &lt;a href="http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/"&gt;safe navigation operator&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  In Practice
&lt;/h3&gt;

&lt;p&gt;There are a few patterns that emerge when writing a React app. Optional props mean you're often doing null checks and &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; chaining to ensure a prop exists before accessing it.&lt;/p&gt;

&lt;p&gt;For some of our reusable components, we have optional render props to render any context-specific supplementary content. This ends up looking something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderSupplementaryContent&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderSupplementaryContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With optional chaining, this becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderSupplementaryContent&lt;/span&gt;&lt;span class="p"&gt;?.()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Not bad! A similar pattern can occur when trying to access properties of an optional prop. Here's a snippet of our code for rendering pull request approvals, in which &lt;code&gt;props.update&lt;/code&gt; may not exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getOverlay&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&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="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;`Approved by &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;approvedBy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&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="s2"&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;With optional chaining, this becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getOverlay&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;`Approved by &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;approvedBy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&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="s2"&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;These are just a few examples in a React app that this new feature will be helpful. Although simple, it removes a lot of boilerplate and helps readability, so it's easy to see why this was one of their most requested features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nullish Coalescing
&lt;/h2&gt;

&lt;p&gt;Although the name sounds a little intimidating, this feature is simple: the new &lt;code&gt;??&lt;/code&gt; operator provides a way to fall back to a default value in a more reliable manner than &lt;code&gt;||&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since &lt;code&gt;||&lt;/code&gt; triggers implicit type coercion, any falsy value at the beginning will be passed over for the following value. With the new &lt;code&gt;??&lt;/code&gt; operator, it will only fall back to the subsequent value if the first value is truly &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  In Practice
&lt;/h3&gt;

&lt;p&gt;We recently added &lt;a href="https://twitter.com/monolistapp/status/1177271203163074562"&gt;full diff browsing and commenting support&lt;/a&gt; to Monolist.&lt;/p&gt;

&lt;p&gt;One obvious requirement of this feature is the ability to map comment threads back to their original line in the git diffs. When doing this, we're often doing comparisons on the relevant line numbers. Here's an example utility function we use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getLineNumberForChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IChange&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newLineNumber&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oldLineNumber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This means that whenever we pass in a &lt;code&gt;change&lt;/code&gt; (a single line of a git diff), we return either its newest line number, or if that doesn't exist then fall back to its old line number. For now this works because our line number indices start at 1. However, if &lt;code&gt;newLineNumber&lt;/code&gt; were ever &lt;code&gt;0&lt;/code&gt;, we'd pass right over it and erroneously return &lt;code&gt;oldLineNumber&lt;/code&gt;. We can now fix this easily with nullish coalescing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getLineNumberForChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IChange&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newLineNumber&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oldLineNumber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will &lt;strong&gt;only&lt;/strong&gt; return &lt;code&gt;newLineNumber&lt;/code&gt; if it's not explicitly &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;undefined&lt;/code&gt;! No more skipping over &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assertion Functions
&lt;/h2&gt;

&lt;p&gt;The last "headline" feature in TypeScript 3.7 that we'll go over is assertion functions. These ensure that whatever condition is being checked must be true for the remainder of the containing scope. These assertion functions can take two forms.&lt;/p&gt;

&lt;p&gt;The first, &lt;code&gt;asserts condition&lt;/code&gt;, says that whatever gets passed as the &lt;code&gt;condition&lt;/code&gt; must be true if the assert returns. Otherwise, an error is thrown. This is similar to Node's &lt;a href="https://nodejs.org/api/assert.html"&gt;assert&lt;/a&gt; module.&lt;/p&gt;

&lt;p&gt;The second, &lt;code&gt;asserts val is &amp;lt;type&amp;gt;&lt;/code&gt;, doesn't check for a condition but rather that a specific variable or property has a different type. These are Similar to &lt;a href="http://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicates"&gt;type predicates&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  In Practice
&lt;/h3&gt;

&lt;p&gt;Since Monolist integrates with many different applications and displays them all in one similar format, we have many different item types that contribute to one union type: &lt;code&gt;ActionItem&lt;/code&gt;. This means there are many places where we have to check the type of the item before we proceed with integration-specific logic.&lt;/p&gt;

&lt;p&gt;Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getActionsForGithubPullRequestActionItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actionItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ActionItem&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Action&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;possibleActions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&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;actionItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actionItemType&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;githubPullRequest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&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;_actionItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;actionItem&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;GithubPullRequestActionItem&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;_actionItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_actionItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;githubPullRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canBeApproved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;possibleActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;approve&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;possibleActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;merge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;possibleActions&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;Here, we're getting the available actions that a user can take on their GitHub pull request items. However, we first have to ensure that the item is the type we expect: a &lt;code&gt;githubPullRequest&lt;/code&gt;. This requires first checking the type of the item, and then re-aliasing it as the proper type so that our later property accesses don't throw (like &lt;code&gt;actionItem.githubPullRequest.canBeApproved&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Using the second assertion function signature, we can create an assertion function to be re-used in places like this moving forward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;assertIsGithubPullRequestItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ActionItem&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;asserts&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;GithubPullRequestActionItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;actionItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actionItemType&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;githubPullRequest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AssertionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not a GitHub pull request item!&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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getActionsForGithubPullRequestActionItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actionItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ActionItem&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;assertIsGithubPullRequestItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actionItem&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;possibleActions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&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;actionItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actionItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;githubPullRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canBeApproved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;possibleActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;approve&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;possibleActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;merge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;possibleActions&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;Now, assuming our newly added assertion function doesn't throw, the rest of &lt;code&gt;getActionsForGithubPullRequestActionItem&lt;/code&gt; will know that &lt;code&gt;actionItem&lt;/code&gt; is a &lt;code&gt;GithubPullRequestActionItem&lt;/code&gt;. Again, this is similar to what can be achieved with type predicates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;These are just a few of the new features being added to TypeScript regularly. Read their full announcement &lt;a href="https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-beta/"&gt;here&lt;/a&gt;, and &lt;a href="http://eepurl.com/gFxG3n"&gt;subscribe to our mailing list&lt;/a&gt; to keep up to date on any of our future posts.&lt;/p&gt;

&lt;h2&gt;
  
  
  ❗️ Are you a software engineer?
&lt;/h2&gt;

&lt;p&gt;At Monolist, we're building software to help engineers be their most productive. If you want to try it for free, just &lt;a href="https://app.monolist.co/login"&gt;click here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>Building a Subscription Billing System From Scratch with Rails and Stripe</title>
      <dc:creator>Jake Marsh</dc:creator>
      <pubDate>Wed, 02 Oct 2019 17:02:55 +0000</pubDate>
      <link>https://forem.com/jakemmarsh/building-a-subscription-billing-system-from-scratch-with-rails-and-stripe-3o2k</link>
      <guid>https://forem.com/jakemmarsh/building-a-subscription-billing-system-from-scratch-with-rails-and-stripe-3o2k</guid>
      <description>&lt;p&gt;As an early-stage startup building a paid product, there’s an entire feature-set you’ll inevitably have to build out: a billing system. Although this can (and should) be extremely basic in your first iteration, some time and thought should still be taken to ensure it’s done correctly and you’re set up for success moving forward. The last thing you want to be debugging or struggling to scale is your ability to take people’s money.&lt;/p&gt;

&lt;p&gt;Luckily, there are a huge number of APIs and tools that make building out a simple billing system fairly straightforward. Our personal favorite is &lt;a href="https://stripe.com/"&gt;Stripe&lt;/a&gt;.&lt;br&gt;
Not only do they offer billing APIs and dashboards for likely everything you could need, but their documentation and developer ecosystem is also known for being extremely good. This is all not to mention their suite of services to help early startups like &lt;a href="https://stripe.com/atlas"&gt;Atlas&lt;/a&gt;, or their developer tools like &lt;a href="https://monolist.co/blog/2019/09/getting-started-sorbet/"&gt;Sorbet&lt;/a&gt;.&lt;/p&gt;




&lt;p class="text--medium text-weight--heavy"&gt;
  Monolist is the command center for engineers — tasks, pull requests, messages, docs — all in one place. &lt;a href="/"&gt;Learn more&lt;/a&gt; or &lt;a href="https://app.monolist.co/login"&gt;try it for free&lt;/a&gt;.
&lt;/p&gt;




&lt;p&gt;We’re going to be diving in and building a simple billing system from scratch. Since we use &lt;a href="https://rubyonrails.org/"&gt;Rails&lt;/a&gt; here at&lt;br&gt;
Monolist and it’s fairly easy to get started with, that’s what we’ll be working with. Per our reasons above, we’ll be using &lt;a href="https://stripe.com/billing"&gt;Stripe Billing&lt;/a&gt;.&lt;br&gt;
There will also be a focus on resiliency to ensure we’re handling all possible cases that Stripe may throw at us.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;If you don’t have a Rails app to work with, you can read their Getting Started guide &lt;a href="https://guides.rubyonrails.org/getting_started.html"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since we’re using Rails, as always there’s a gem to help us out here. Stripe maintains &lt;a href="https://github.com/stripe/stripe-ruby"&gt;stripe-ruby&lt;/a&gt;, a great library&lt;br&gt;
that should support all of the functionality that we’ll need. Let’s get it installed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add stripe-ruby to your gemfile: &lt;code&gt;gem "stripe"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bundle install&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next, we made to make sure we’re actually including and initializing Stripe in our application.&lt;br&gt;
To do so, let’s add a new file at &lt;code&gt;config/initializers/stripe.rb&lt;/code&gt;. Rails will execute this initializer after all frameworks and plugins are loaded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"stripe"&lt;/span&gt;

&lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"STRIPE_PRIVATE_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Pretty simple, right? To generate the &lt;code&gt;STRIPE_PRIVATE_API_KEY&lt;/code&gt;, follow their documentation &lt;a href="https://stripe.com/docs/keys"&gt;here&lt;/a&gt;. It’s generally good to store&lt;br&gt;
these kinds of keys in an environment variable, but it could also be hardcoded here for expediency. &lt;strong&gt;Just make sure not to check it into source control!&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating our models
&lt;/h2&gt;

&lt;p&gt;At the lowest level, the core Stripe resources we’ll be working with are a &lt;a href="https://stripe.com/docs/api/service_products"&gt;Product&lt;/a&gt; and &lt;a href="https://stripe.com/docs/api/customers"&gt;Customers&lt;/a&gt;. In this walkthrough, we’ll be assuming we have just one product representing our subscription application.&lt;/p&gt;

&lt;p&gt;First, let’s write the migration and create the corresponding models. &lt;strong&gt;In all of the migrations below, the purpose of the column is provided in the comments.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddBillingEntities&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;5.2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:billing_products&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:stripeid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;            &lt;span class="c1"&gt;# To map to the Product in Stripe&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:stripe_product_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# The name of the product in Stripe&lt;/span&gt;

      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:billing_customers&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;

      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:stripeid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# To map to the Customer in Stripe&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:default_source&lt;/span&gt;        &lt;span class="c1"&gt;# Stripe identifier for the user's default payment source (initially null)&lt;/span&gt;

      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BillingProduct&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:billing_plans&lt;/span&gt; &lt;span class="c1"&gt;# Ignore this for now, we'll be adding this later&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BillingCustomer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;required: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:billing_subscriptions&lt;/span&gt; &lt;span class="c1"&gt;# Ignore this for now, we'll be adding this later&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To associate our product with a price and recurrence period, we’ll next create an entity to correspond to our &lt;a href="https://stripe.com/docs/api/plans"&gt;Plan&lt;/a&gt;.&lt;br&gt;
We should also then ensure we’ve added the has_many association to &lt;code&gt;BillingProduct&lt;/code&gt; that we commented on in the snippet above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddBillingPlans&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;5.2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:billing_plans&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:billing_product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;              &lt;span class="c1"&gt;# The BillingProduct that the BillingPlan belongs to&lt;/span&gt;

      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:stripeid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;                         &lt;span class="c1"&gt;# To map to the Plan in Stripe&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:stripe_plan_name&lt;/span&gt;                              &lt;span class="c1"&gt;# The name of the plan in Stripe&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decimal&lt;/span&gt; &lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;precision: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;scale: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# Price of the plan, in the corresponding currency's smallest unit (i.e., cents)&lt;/span&gt;

      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BillingPlan&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:billing_product&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:billing_subscriptions&lt;/span&gt; &lt;span class="c1"&gt;# Ignore this for now, we'll be adding this later&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Lastly, we need a way to track our &lt;a href="https://stripe.com/docs/api/subscriptions"&gt;Subscriptions&lt;/a&gt;. These map a specific customer to a product via a plan.&lt;br&gt;
This is also when we should ensure we’ve added the has_many associations to &lt;code&gt;BillingCustomer&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;BillingPlan&lt;/code&gt; that were commented on above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddBillingSubscriptions&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;5.2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:billing_subscriptions&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:billing_plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;     &lt;span class="c1"&gt;# The BillingPlan that the BillingSubscription belongs to&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:billing_customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# The BillingCustomer that the BillingSubscription belongs to&lt;/span&gt;

      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:stripeid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;             &lt;span class="c1"&gt;# To map to the Subscription in Stripe&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;               &lt;span class="c1"&gt;# The status of the Stripe subscription (trialing, active, etc.)&lt;/span&gt;

      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;datetime&lt;/span&gt; &lt;span class="ss"&gt;:current_period_end&lt;/span&gt;              &lt;span class="c1"&gt;# When the current subscription period will lapse&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;datetime&lt;/span&gt; &lt;span class="ss"&gt;:cancel_at&lt;/span&gt;                       &lt;span class="c1"&gt;# If set to cancel, when the cancellation will occur&lt;/span&gt;

      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BillingSubscription&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:billing_plan&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:billing_customer&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Hooking up our core models
&lt;/h2&gt;

&lt;p&gt;Now let’s write some services that will help us synchronize our entities with their counterparts on Stripe.&lt;/p&gt;

&lt;p&gt;Generally, you won’t be creating or modifying Products or Plans very often. However, we still need to be able to synchronize them quickly and easily with Stripe to ensure we’re never out of sync and causing billing issues for our users. This includes accounting for the various updates that could happen: a Product or Plan can be created, updated, or deleted.&lt;/p&gt;

&lt;p&gt;Let’s write a couple of classes to do just that. We’ve commented out the blocks of code for clarity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SynchronizeBillingProducts&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="c1"&gt;# First, we gather our existing products&lt;/span&gt;
    &lt;span class="n"&gt;existing_products_by_stripeid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BillingProduct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_with_object&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stripeid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# We're also going to keep track of the products we confirm exist on Stripe's end&lt;/span&gt;
    &lt;span class="n"&gt;confirmed_existing_stripeids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="c1"&gt;# Fetch all of our active products from Stripe&lt;/span&gt;
    &lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})[&lt;/span&gt;&lt;span class="s2"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="c1"&gt;# If we are already aware of the product. let's just update the non-static fields on our end&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;existing_products_by_stripeid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
        &lt;span class="n"&gt;existing_products_by_stripeid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;stripe_product_name: &lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="c1"&gt;# If we're not already aware of the product, let's create it on our end&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="no"&gt;BillingProduct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="ss"&gt;stripeid: &lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="ss"&gt;stripe_product_name: &lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;confirmed_existing_stripeids&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# Lastly, delete any products on our end that no longer exist (or are not active) on Stripe&lt;/span&gt;
    &lt;span class="no"&gt;BillingProduct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;not&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;stripeid: &lt;/span&gt;&lt;span class="n"&gt;confirmed_existing_stripeids&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;destroy_all&lt;/span&gt;

    &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SynchronizeBillingPlans&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="c1"&gt;# First, we gather our existing plans&lt;/span&gt;
    &lt;span class="n"&gt;existing_plans_by_stripeid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BillingPlan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_with_object&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stripeid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# We're also going to keep track of the plans we confirm exist on Stripe's end&lt;/span&gt;
    &lt;span class="n"&gt;confirmed_existing_stripeids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="c1"&gt;# Fetch all of our active plans from Stripe&lt;/span&gt;
    &lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})[&lt;/span&gt;&lt;span class="s2"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="c1"&gt;# If we are already aware of the plan, let's just update the non-static fields on our end&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;existing_plans_by_stripeid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
          &lt;span class="n"&gt;existing_plans_by_stripeid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="ss"&gt;stripe_plan_name: &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"nickname"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="c1"&gt;# If we're not already aware of the plan, let's create it on our end&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="no"&gt;BillingPlan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="ss"&gt;billing_product: &lt;/span&gt;&lt;span class="no"&gt;BillingProduct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;stripeid: &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"product"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="ss"&gt;stripeid: &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="ss"&gt;stripe_plan_name: &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"nickname"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="n"&gt;confirmed_existing_stripeids&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# Lastly, delete any plans on our end that no longer exist (or are not active) on Stripe&lt;/span&gt;
    &lt;span class="no"&gt;BillingPlan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;not&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;stripeid: &lt;/span&gt;&lt;span class="n"&gt;confirmed_existing_stripeids&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;destroy_all&lt;/span&gt;

    &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;These services can be invoked manually when you know you’ve made changes from the Stripe dashboard. We’ll also discuss hooking them up to webhooks below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🛠 Before moving on, create a Product and Plan in Stripe and call your new services. They should now be in your database as &lt;code&gt;BillingProduct&lt;/code&gt;s and &lt;code&gt;BillingPlan&lt;/code&gt;s!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Subscribing a user
&lt;/h2&gt;

&lt;p&gt;Now that we’re able to synchronize our core billing entities, we can get started actually subscribing a user to our product via one of the plans.&lt;/p&gt;

&lt;p&gt;First, we need to be able to create a Customer for one of our users. To create a paid customer, this requires a Stripe &lt;a href="https://stripe.com/docs/api/tokens"&gt;token&lt;/a&gt;,&lt;br&gt;
most often generated client-side using one of their fantastic &lt;a href="https://stripe.com/payments/elements"&gt;JavaScript libraries&lt;/a&gt;.&lt;br&gt;
A Customer can be created without a token, but will not be eligible for any paid products (only free or free trials).&lt;/p&gt;

&lt;p&gt;We’re going to assume here that you have a &lt;code&gt;User&lt;/code&gt; class with both &lt;code&gt;email&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; fields.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateStripeBillingCustomer&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="ss"&gt;stripe_token: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# First, we fetch all users with the same email&lt;/span&gt;
    &lt;span class="n"&gt;existing_customers_with_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt; &lt;span class="p"&gt;})[&lt;/span&gt;&lt;span class="s2"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# If we've found any matching customers for the user, grab it&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;existing_customers_with_email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;positive?&lt;/span&gt;
      &lt;span class="n"&gt;stripe_customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;existing_customers_with_email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
    &lt;span class="c1"&gt;# Otherwise, let's create a new customer for the user&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;stripe_customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;source: &lt;/span&gt;&lt;span class="n"&gt;stripe_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# Lastly, let's make sure we persist the customer on our end&lt;/span&gt;
    &lt;span class="no"&gt;BillingCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;stripeid: &lt;/span&gt;&lt;span class="n"&gt;stripe_customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;default_source: &lt;/span&gt;&lt;span class="n"&gt;stripe_customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we’re able to create a Customer and associate it with one of our users, we’re ready to subscribe them to a plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🛠 Before moving on, try to subscribe a test user to one of your products (invoke a service manually if necessary). The subscription should now appear in your Stripe dashboard, and your user should have a &lt;code&gt;BillingCustomer&lt;/code&gt; with a &lt;code&gt;BillingSubscription&lt;/code&gt;!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Staying in sync using webhooks
&lt;/h2&gt;

&lt;p&gt;The last thing we’re going to cover is handling &lt;a href="https://stripe.com/docs/billing/webhooks"&gt;webhooks&lt;/a&gt; from Stripe when anything occurs or changes&lt;br&gt;
related to any part of our billing system. This ensures we’ll always stay in sync and avoid any unknown billing bugs or issues.&lt;/p&gt;

&lt;p&gt;First, you’ll need to activate webhooks and specify an endpoint in your &lt;a href="https://dashboard.stripe.com/webhooks"&gt;Stripe dashboard&lt;/a&gt;. When doing so, you’ll&lt;br&gt;
be required to specify the types of events you want to be notified about. For the purposes of this walkthrough, these are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;plan.updated&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;plan.deleted&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;plan.created&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;product.updated&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;product.deleted&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;product.created&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customer.updated&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customer.deleted&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customer.subscription.updated&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customer.subscription.deleted&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customer.subscription.created&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, we’ll need to add a new route handler at the endpoint you provided to Stripe.&lt;br&gt;
Here we need to both &lt;a href="https://stripe.com/docs/webhooks/signatures"&gt;verify the signature&lt;/a&gt; of the webhook request as well as handle the event itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StripeWebhooksController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;receive&lt;/span&gt;
    &lt;span class="c1"&gt;# Get the event payload and signature&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;
    &lt;span class="n"&gt;sig_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Stripe-Signature"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Attempt to verify the signature. If successful, we'll handle the event&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Webhook&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Signature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sig_header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"STRIPE_WEBHOOK_SIGNING_KEY"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="no"&gt;HandleStripeEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# If we fail to verify the signature, then something was wrong with the request&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SignatureVerificationError&lt;/span&gt;
      &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# We've successfully processed the event without blowing up&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You’ll notice there’s a new environment variable here: &lt;code&gt;STRIPE_WEBHOOK_SIGNING_KEY&lt;/code&gt;. This should be available in your Stripe dashboard for the webhooks endpoint you just created.&lt;/p&gt;

&lt;p&gt;Lastly, we need to make sure we’re handling each of the individual events we’ve subscribed to. We’re not going to go into detail here on each of the handlers we’ve implemented, as you should now have the basis to move forward and write those yourselves.&lt;/p&gt;

&lt;p&gt;The few exceptions are those related to Products and Plans: since we expect these to be updated so rarely, we can just re-invoke our overall synchronizer services that we wrote earlier!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HandleStripeEvent&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"product.created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"product.updated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"product.deleted"&lt;/span&gt;
      &lt;span class="no"&gt;SynchronizeBillingProducts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"plan.created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"plan.updated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"plan.deleted"&lt;/span&gt;
      &lt;span class="no"&gt;SynchronizeBillingPlans&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"customer.updated"&lt;/span&gt;
      &lt;span class="no"&gt;HandleStripeCustomerUpdatedEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;event: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"customer.deleted"&lt;/span&gt;
      &lt;span class="no"&gt;HandleStripeCustomerDeletedEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;event: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"customer.subscription.updated"&lt;/span&gt;
      &lt;span class="no"&gt;HandleStripeSubscriptionUpdatedEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;event: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"customer.subscription.deleted"&lt;/span&gt;
      &lt;span class="no"&gt;HandleStripeSubscriptionDeletedEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;event: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"customer.subscription.created"&lt;/span&gt;
      &lt;span class="no"&gt;HandleStripeSubscriptionCreatedEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;event: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Unexpected event from Stripe: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🛠 Now that we’re handling these various events from Stripe, you should now be able to update any of your entities (Product, Plan, Customer, Subscription) in the Stripe dashboard and see the changes reflected on your end.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;This walkthrough should get you set up with a simple subscription billing system so that you can start charging for your product.&lt;/p&gt;

&lt;p&gt;In a later blog post, we’ll talk about doing something a little more complex: incentivizing in-app actions (such as referrals) with coupons on the user’s billing plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  ❗️ Are you a software engineer?
&lt;/h2&gt;

&lt;p&gt;At Monolist, we're building software to help engineers be their most productive.&lt;br&gt;
If you want to try it for free, just &lt;a href="https://app.monolist.co/login"&gt;click here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>startup</category>
      <category>ruby</category>
      <category>rails</category>
      <category>stripe</category>
    </item>
    <item>
      <title>Optimizing for Iteration: Best Practices for Automating Everything</title>
      <dc:creator>Jake Marsh</dc:creator>
      <pubDate>Thu, 26 Sep 2019 16:37:14 +0000</pubDate>
      <link>https://forem.com/jakemmarsh/optimizing-for-iteration-best-practices-for-automating-everything-10le</link>
      <guid>https://forem.com/jakemmarsh/optimizing-for-iteration-best-practices-for-automating-everything-10le</guid>
      <description>&lt;p&gt;As we’ve discussed in a &lt;a href="https://monolist.co/blog/2019/01/product-management/"&gt;previous blog post&lt;/a&gt;, product development is hypothesis testing. This is especially true in the early stages of a company when you need to confirm or reject your hypothesis as quickly as possible.&lt;br&gt;
This process is then repeated until you (hopefully) reach product-market fit. &lt;strong&gt;To get there, your team needs to be able to work and build at a pace that allows for this constant and rapid iteration.&lt;/strong&gt;&lt;/p&gt;




&lt;p class="text--medium text-weight--heavy"&gt;
  Monolist is the command center for engineers — tasks, pull requests, messages, docs — all in one place. &lt;a href="/"&gt;Learn more&lt;/a&gt; or &lt;a href="https://app.monolist.co/login"&gt;try it for free&lt;/a&gt;.
&lt;/p&gt;



&lt;h2&gt;
  
  
  📝 Document what needs to be documented
&lt;/h2&gt;

&lt;p&gt;Documentation is one possibly un-obvious form of automation. For everything that’s well-documented, a later employee doesn’t need to waste time figuring it out on their own. However, creating and maintaining documentation requires a fair amount of time from members of the team.&lt;/p&gt;

&lt;p&gt;A happy medium we’ve found here at Monolist is to only focus on documentation for anything that falls into the category of “one-time process” documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-Time Process Documentation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a new engineer joins the team there’s likely to be a large number of tools, services, and permissions to configure manually before they can truly get to work. This can be a long and painful process, especially for the new employee excited to start building.&lt;br&gt;
To avoid this, as much of the process should be documented as possible. This ensures future engineers have a guidebook to reference and don’t have to waste time finding solutions for themselves.&lt;/p&gt;

&lt;p&gt;Similar to the onboarding process, there are likely other processes that occur rarely enough that full automation is not worth the effort. One example is deploying an internal service that rarely changes. For cases like these,&lt;br&gt;
it’s good to maintain documentation so that anyone on the team is able to execute the process in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙ Automate all of your tests
&lt;/h2&gt;

&lt;p&gt;Testing should be a priority from day one, and your test suite should be run often (ideally on every commit). With those two requirements in mind, it doesn’t make sense to have to run any part of your test suite manually.&lt;br&gt;
If it’s hard to do and you have to do it often, someone will inevitably end up skipping the testing step.&lt;/p&gt;

&lt;p&gt;To ensure it’s easy and fast to both write and maintain your automated tests, here are a couple of things to keep in mind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Balancing unit tests versus integration tests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the everlasting struggles in software testing is maintaining a balance between &lt;a href="http://softwaretestingfundamentals.com/unit-testing/"&gt;unit testing&lt;/a&gt; and &lt;a href="http://softwaretestingfundamentals.com/integration-testing/"&gt;integration testing&lt;/a&gt;.&lt;br&gt;
Although they both aim to cover and test the various code paths in your application, their opposite approaches result in different tradeoffs.&lt;/p&gt;

&lt;p&gt;Unit tests serve to cover the more granular functionality of your modules. This means they’re great at testing the smaller details and relatively easy to maintain, but they’re prone to missing gaps between your various modules.&lt;/p&gt;

&lt;p&gt;Integration tests, on the other hand, test your code paths from top to bottom with no (or less) mocking. This means every invoked module is actually run. These tests are more fragile and harder to write or maintain,&lt;br&gt;
but they offer more reassurance that your app is functioning as expected.&lt;/p&gt;

&lt;p&gt;Due to the relative ease of unit testing, you should aim to unit test most of your codebase. Any new or modified code should generally have an associated automated test or test update. When it comes to integration testing,&lt;br&gt;
this should be used more sparingly to confirm the expected behavior of more critical code paths. Your onboarding flow, for example, is a crucial part of the new user experience and so is likely worth integration testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing implementation-independent tests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lastly, another key point to remember when writing your automated tests is to ensure they’re as implementation-independent as possible. This means that whenever possible,&lt;br&gt;
a module should be tested by asserting strictly on its input and outputs rather than by asserting on the internal behaviors or calls of that module.&lt;/p&gt;

&lt;p&gt;The reason for this is that as your team is moving quickly and iterating often, you’re going to be refactoring large swaths of your codebase fairly frequently. The less your code has to change,&lt;br&gt;
in this case your tests, the quicker you’ll be able to make these changes.&lt;/p&gt;

&lt;h5&gt;
  
  
  🙌 Our Recommendations
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/testing-library/react-testing-library"&gt;react-testing-library&lt;/a&gt;&lt;/strong&gt;: Although this specific library is for React, the same &lt;a href="https://github.com/testing-library"&gt;testing-library&lt;/a&gt; org has created similar libraries for many other frameworks.
These aim to eliminate implementation-specific tests by forcing you to interact with your modules the same way a user would.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.cypress.io/"&gt;Cypress&lt;/a&gt;&lt;/strong&gt;: Another Javascript library, but for end-to-end (integration) testing your application as a whole. This is the most painless experience I’ve ever had writing resilient integration tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💨 Streamline code review
&lt;/h2&gt;

&lt;p&gt;Code review should be a central part of your development process. Peer reviews ensure the team is aligned on and aware of any changes, while also providing the opportunity to catch any bugs or issues.&lt;br&gt;
The importance of code review means it’s a very frequent occurrence for the average engineer. When they’re reviewing pull requests so often, it’s best to optimize and automate that process as much as possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automate discovery of pull requests and their updates&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The current way most engineers become aware of a new pull request to review is through email or Slack. Although these methods are instant, they’re easy to miss and are immediately out-of-date.&lt;br&gt;
This results in engineers unaware of pull requests they’re blocking, and their teammates blocked from further progress.&lt;/p&gt;

&lt;p&gt;As much as possible, your team should optimize the process of discovering pull requests or updates/comments to those pull requests. This will prevent engineers from blocking each other and ensure everyone is working with the same context.&lt;br&gt;
It may take iteration to discover what works best for your team, as every approach will have its drawbacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Eliminate discussion about non-crucial details&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When it comes to reviewing the code of your teammates, it’s often easy to fall into the trap of commenting on the obvious things: style and formatting. These comments cause unrelated discussion that not only do not&lt;br&gt;
require actual comprehension of the PR, but also waste the time of the engineers involved.&lt;/p&gt;

&lt;p&gt;Whenever possible, these decisions and discussions should be automated away. One common practice is through the use of &lt;a href="https://en.wikipedia.org/wiki/Lint_(software)"&gt;linters&lt;/a&gt;: automated processes to compare your codebase and any&lt;br&gt;
updates against a set of rules you define. A newer approach that takes it a step further is auto-formatting all code in a pre-commit hook, completely eliminating any further thought or discussion about things such as formatting.&lt;/p&gt;

&lt;h5&gt;
  
  
  🙌 Our Recommendations
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://monolist.co/"&gt;Monolist&lt;/a&gt;&lt;/strong&gt;: Designed to be an engineer’s command center, Monolist synchronizes in real time to help them discover their pull requests, messages, and tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://prettier.io/"&gt;Prettier&lt;/a&gt;&lt;/strong&gt;: As someone who’s rather opinionated about their code styles, I was reluctant to try Prettier. We’ve now fully adopted it and I’ll never go back to manual formatting or style debates in pull requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚢 Ship easily and often
&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, your goal as an early-stage startup should be to iterate and ship as often and as quickly as possible to maximize learning. This means that you should be able to deploy a new version across all platforms (API, web, mobile) multiple times a day.&lt;br&gt;
The only way to achieve this, without dedicating a large amount of manual time and effort, is through complete automation of your deployment process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI/CD&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.infoworld.com/article/3271126/what-is-cicd-continuous-integration-and-continuous-delivery-explained.html"&gt;Continuous integration/continuous delivery&lt;/a&gt; allows us to achieve this full automation of the deployment process. By automating the entire process from commit, to testing, to building, to deploying,&lt;br&gt;
you’re eliminating the need to ever do it manually and enabling your team to merge and deploy often.&lt;/p&gt;

&lt;h5&gt;
  
  
  🙌 Our Recommendations
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://about.gitlab.com/"&gt;GitLab&lt;/a&gt;&lt;/strong&gt;: GitLab’s all-in-one approach to code management and simple CI pipelines makes it extremely easy to get started with CI and integrate it gradually across your existing process or codebase.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔥 Catch fires automatically
&lt;/h2&gt;

&lt;p&gt;Naturally, the last thing we’ll talk about automating is ensuring we catch, handle, and triage any errors or abnormalities once the app is out in the wild. With a small, fast team it’s inevitable that you’ll ship a bug here or there.&lt;br&gt;
What’s important is quickly recognizing and addressing the issue so your users are not experiencing sustained difficulty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitoring&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first half of continued app reliability is accurate and reliable monitoring. In the early stages, monitoring should be done for things like server health (CPU usage, RAM usage) and general app health (can the app be loaded by a user? Are async jobs processing?). Any exceptions triggered in the app (API or client) should also be tracked.&lt;/p&gt;

&lt;p&gt;Although it can be valuable to have metrics around actual user statistics (posts liked, comments created), these should not be relied upon for any alerting as they’re likely to be volatile in the early stages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alerting&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you have sufficient monitoring in place, thresholds can be defined for the various metrics you’re tracking. When these thresholds are violated, the team (or a responsible engineer) should be automatically notified to ensure that the issue is investigated and addressed if necessary.&lt;/p&gt;

&lt;h5&gt;
  
  
  🙌 Our Recommendations
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://sentry.io/welcome/"&gt;Sentry&lt;/a&gt;&lt;/strong&gt;: Sentry provides automatic exception reporting for a large variety of languages and frameworks. We use it for both Rails and React to capture and centralize our exceptions for triaging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt;&lt;/strong&gt;: A powerful, customizable open-source tool for monitoring your services. Can be configured to handle alerts in many days, i.e. sending to &lt;a href="https://www.pagerduty.com/"&gt;PagerDuty&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ❗️ Are you a software engineer?
&lt;/h2&gt;

&lt;p&gt;At Monolist, we're following these tips to rapidly ship new features that help software engineers&lt;br&gt;
be their most productive. If you want to try it for free, just &lt;a href="https://app.monolist.co/login"&gt;click here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>startup</category>
      <category>devops</category>
    </item>
    <item>
      <title>Running Effective Bug Bashes That Your Team Won’t Dread</title>
      <dc:creator>Jake Marsh</dc:creator>
      <pubDate>Mon, 23 Sep 2019 16:44:08 +0000</pubDate>
      <link>https://forem.com/jakemmarsh/running-effective-bug-bashes-that-your-team-won-t-dread-20bg</link>
      <guid>https://forem.com/jakemmarsh/running-effective-bug-bashes-that-your-team-won-t-dread-20bg</guid>
      <description>&lt;p&gt;“Bug bash” is a term thrown around often in software engineering and product development. In general, it means all stakeholders in a project placing all of their focus into finding (and resolving) any outstanding bugs in a feature or product.&lt;/p&gt;

&lt;p&gt;Some companies or teams conduct bug bashes in a reactionary manner, meaning they pause everything when they’ve accumulated too many bugs in the product. At that point, they tell everyone involved to go out and “bash” as many bugs as they can. Here at Monolist, we like to conduct them proactively: sitting down and working through everything together, in an organized manner, right before we release something new to our users.&lt;/p&gt;

&lt;h4&gt;
  
  
  We already have (QA|automated testing). Do we really need to do bug bashes too?
&lt;/h4&gt;

&lt;p&gt;If your company has dedicated QA engineers whose job it is to manually test each and every new feature from top to bottom, then you’ve got it made (and if you’re one of them, I’m sorry). This does probably mean a bug bash, as we define it at Monolist, is unnecessary for your team. But they can never hurt!&lt;/p&gt;

&lt;p&gt;Automated test coverage, on the other hand, is the direction that more and more software companies are moving. This often means a combination of unit testing and integration testing (among various other approaches). Although we are firm believers in automating everything here at Monolist, we still aim to bug bash every new feature we ship as a team to do the following:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Catch unknown edge cases.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No matter how comprehensive your test suite is, it’s still likely to have some gaps. With unit tests, as is hinted at by its name, you’re testing individual units and missing any of the logic in between. Integration testing aims to solve this, but still requires you to define and properly test every possible user path. Although you should always strive for that goal, it may not always be realistic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Get all stakeholders in front of the final product.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We all dream of the perfect product process in which the feature is well defined from the beginning, no requirements change, and everything is delivered as expected. In an ideal world, all product questions should be raised and addressed before you reach the bug bash. Unfortunately, that’s rarely reality.&lt;/p&gt;

&lt;p&gt;A final bug bash with all stakeholders involved (product, design, engineering, etc.) ensures that everyone is getting to see the product in its (near) final state. This should help to ensure that any discrepancies or miscommunications that have slipped through the preceding product process can be ironed out before public release.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to maximize stress and minimize efficacy
&lt;/h1&gt;

&lt;p&gt;The very nature of a bug bash lends itself to stress and anxiety. After all, you’re sitting down with your whole team, poring over the product with a fine-toothed comb, finding and resolving any number of new bugs in a rushed environment. I don’t have the stats, but I assume that the number of engineers excited about that prospect would be fairly low.&lt;/p&gt;

&lt;p&gt;Due to the stress-inducing nature, there are a number of things that you should avoid when conducting a bug bash.&lt;/p&gt;

&lt;h4&gt;
  
  
  Coming unprepared or without a test plan.
&lt;/h4&gt;

&lt;p&gt;Throwing a group of people into a room with the single instruction of “break it” is a pretty ineffective strategy from the get-go. Without coordination between parties, you’re likely to end up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple reports of the same bug, wasting the time and energy of both reporter and reportee.&lt;/li&gt;
&lt;li&gt;Missed bugs, because without a plan, there’s no chance your team will get through all of the test-cases.&lt;/li&gt;
&lt;li&gt;A stressed and confused team trying to play whack-a-mole with the bugs they’re hearing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lacking organization around the bug discovery/reporting process.&lt;br&gt;
When you do have multiple people all looking to find and report issues with a product, this can quickly devolve into any number of them vying for the attention of the relevant stakeholder, or calling out their issues, or asking the people around them if they’re seeing the same thing. This all distracts from the actual bug bash, and makes it more likely that the team will miss real issues.&lt;/p&gt;

&lt;h4&gt;
  
  
  Allowing non-stakeholders to participate (or even be in the room).
&lt;/h4&gt;

&lt;p&gt;Other parties who are not involved in the building and shipping of the feature should not be in the room for the bug bash (sorry). Without the context involved with the project, they’re likely to interject thoughts or opinions that have no place in a bug bash, and that are likely to cause stress, confusion, or misdirection late in the process.&lt;/p&gt;

&lt;h4&gt;
  
  
  Trying to discuss or address feedback unrelated to bugs, such as on the design or user experience.
&lt;/h4&gt;

&lt;p&gt;When issues arise in the bug bash unrelated to actual bugs, i.e. design tweaks or UX concerns, it’s easy to get distracted and dive into a conversation about that specific issue. Although it should be tracked and addressed, a bug bash is not the best forum.&lt;/p&gt;

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

&lt;p&gt;Now that we’ve discussed the various things you should not do when organizing and running a bug bash, here are some of the things we’ve codified at Monolist to ensure all bug bashes run smoothly, are frustration-free, and address as many outstanding issues as possible.&lt;/p&gt;

&lt;h4&gt;
  
  
  Come prepared with a formal list of user stories and/or specific paths that need to be tested.
&lt;/h4&gt;

&lt;p&gt;These should be as granular as possible, as if to serve as a step-by-step guide for the tester(s). If you’ve already written a thorough product spec outlining the various user stories, this can probably double as your testing plan. If you haven’t already written a product spec (tsk tsk), writing the test plan might help.&lt;/p&gt;

&lt;p&gt;Below is a screenshot of our actual test plan for our recent Asana integration revamp. As you can see, it is quite simple. All it does is lay out clearly which specific scenarios need to be tested, and what the expected outcome is. You can even tell we had a bug bash follow-up from the story outlined in red.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F70asi9ifgcypm6y90zqo.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F70asi9ifgcypm6y90zqo.png" alt="Asana test plan"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Assign roles.
&lt;/h4&gt;

&lt;p&gt;As discussed earlier, it’s not ideal to throw everyone into a room and have them start bashing away. There should be a set of pre-assigned roles for the stakeholders, dividing responsibilities to ensure a smooth process. At Monolist, we use the roles below:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Actor”&lt;/strong&gt; - One person responsible for acting out all of the outlined user stories within your application.&lt;br&gt;
&lt;strong&gt;“Conductor”&lt;/strong&gt; - One person responsible for walking the actor (and larger group) through the test plan in the necessary order.&lt;br&gt;
&lt;strong&gt;“Recorder”&lt;/strong&gt; - One person responsible for recording and assigning any and all bugs that do occur.&lt;/p&gt;

&lt;p&gt;If you have any remaining stakeholders involved in the bug bash, they should be paying close attention for any possible product issues, visual bugs, or anything else that should be noted.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create a task for any issue that you encounter. Assign it immediately.
&lt;/h4&gt;

&lt;p&gt;As the actor proceeds through the test plan, the recorder should be paying close attention for any bugs that occur or issues that are raised. These should immediately be tracked and assigned in whatever task management system your team uses. This is to ensure that nothing is forgotten about later.&lt;/p&gt;

&lt;h4&gt;
  
  
  If necessary, pause and immediately schedule the follow-up bug bash.
&lt;/h4&gt;

&lt;p&gt;There’s always that occasional bug bash where the first bug that occurs is quite early in the experience, preventing the bug bash from continuing. If the cause is not immediately obvious or the bug cannot quickly be resolved, the bug bash should be paused. You don’t want to make everyone wait or put the audience pressure on the responsible engineer. Before pausing, though, be sure to schedule the follow-up at a time when all parties will again be present.&lt;/p&gt;

&lt;h4&gt;
  
  
  Always start from the top.
&lt;/h4&gt;

&lt;p&gt;When your team is ready for a follow-up bug bash, always be sure to start from the very beginning of the test plan regardless of how far you made it last time. Any tweaks or bug fixes could have affected previously tested paths, and so everything should be covered.&lt;/p&gt;

&lt;h1&gt;
  
  
  In Conclusion...
&lt;/h1&gt;

&lt;p&gt;As your team continues to conduct bug bashes, always pay attention to how effective they are and how the team feels about them. The process can and should be tweaked along the way.&lt;/p&gt;

&lt;p&gt;Although bug bashes are a manual effort and should never fully replace your other testing approaches, they’re a worthwhile method of testing your new feature end-to-end just as a user would. The key is to have a system and stick to it, ensuring your bug bashes are efficient and valuable for everyone involved.&lt;/p&gt;

&lt;p&gt;Monolist is a global inbox for engineers. &lt;a href="https://app.monolist.co/login" rel="noopener noreferrer"&gt;Try it out now!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
    </item>
    <item>
      <title>Optimizing for Iteration: Choosing Your Early Startup's Tech Stack</title>
      <dc:creator>Jake Marsh</dc:creator>
      <pubDate>Thu, 19 Sep 2019 19:21:10 +0000</pubDate>
      <link>https://forem.com/jakemmarsh/optimizing-for-iteration-choosing-your-early-startup-s-tech-stack-5ck7</link>
      <guid>https://forem.com/jakemmarsh/optimizing-for-iteration-choosing-your-early-startup-s-tech-stack-5ck7</guid>
      <description>&lt;p&gt;Product development is hypothesis testing. This is especially true in the early stages of a company when you need to confirm or reject your hypothesis as quickly as possible.&lt;br&gt;
This process is then repeated until you (hopefully) reach product-market fit. &lt;strong&gt;To get there, your team needs to be able to work and build at a pace that allows for this constant and rapid iteration.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your company’s tech stack will immediately be a fundamental part of how your engineering team works and operates. This is even more important in a small company where the engineering team represents&lt;br&gt;
an outsize portion of the team as a whole.&lt;/p&gt;

&lt;p&gt;Many people will recommend to early founders that they use the technologies they know and write in whatever language they're familiar with. Although this is good advice, like all other decisions it should be weighed carefully.&lt;br&gt;
While ideally you're fully familiar with the stack you choose in order to move as fast as you can, you should also be putting some thought into how these choices will affect you and your engineering team moving forward. For example, it may not always be best to pick the brand new (and unproven) technology on the block when you need to scale quickly and painlessly.&lt;/p&gt;

&lt;h1&gt;
  
  
  What to Optimize For
&lt;/h1&gt;

&lt;p&gt;The overall goal can be summarized as this: &lt;strong&gt;minimize time spent on non-feature engineering work&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For every hour you or another engineer spend configuring, debugging, or learning a new system,&lt;br&gt;
that's an hour not spent iterating on the product to maximize learnings and value delivered to your users.&lt;/p&gt;

&lt;p&gt;Here are the things to aim for when choosing your startup's early tech stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤯 Easy to understand
&lt;/h2&gt;

&lt;p&gt;On a small engineering team, everyone is going to be working everywhere in your codebase. Additionally, if things are going well,&lt;br&gt;
you'll be adding new engineers to the team fairly regularly. For these reasons, your codebase and systems should remain easy to understand and get started with. No one wants to spend multiple days banging their ahead against a spaghetti codebase or tangled dev environment.&lt;/p&gt;

&lt;p&gt;There's a few things that can help with maintaining the grokkability:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Well-known languages or frameworks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Technologies often come and go quickly in software development. However, there are those libraries that are nearly ubiqitous. These are the languages and frameworks&lt;br&gt;
that the majority of software engineers would be at least vaguely familiar with. Examples would be Rails or React, both incredibly popular libraries used and supported by a large number of people and companies. This also means they have large and healthy ecosystems surrounding them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typed languages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Large refactors of core business logic are common when you're an early-stage company iterating often on your product direction. These are no longer nerve wracking when you always know what data you’re working with and when.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self (or well) documented code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While the concept of true "self-documenting code" is &lt;a href="https://www.ericholscher.com/blog/2017/jan/27/code-is-self-documenting/"&gt;fairly controversial&lt;/a&gt;,&lt;br&gt;
it can still be valuable when not taken to an extreme. Write and architect your code so that its purpose is clear and its execution path is easy to follow.&lt;br&gt;
Leave clarifying comments when blocks of code become overly complex out of necessity. As mentioned above, typed languages also help a lot here.&lt;/p&gt;

&lt;h5&gt;
  
  
  🙌 Our Recommendations
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://rubyonrails.org/"&gt;Rails&lt;/a&gt;&lt;/strong&gt; — The simplicity of Rails' &lt;a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller"&gt;MVC model&lt;/a&gt;, coupled with the easy to read and write language Ruby, means it's easy for most engineers to become comfortable working in Rails and shipping production features. Rails also continues to power many well-known, large-scale applications such as Shopify and GitHub.

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Caveat:&lt;/em&gt; Although Ruby is not typed by default, there is a new static type checker called &lt;a href="https://sorbet.org/"&gt;Sorbet&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.typescriptlang.org/"&gt;TypeScript&lt;/a&gt;&lt;/strong&gt; — Defining your types and using TypeScript strictly results in code that’s easy to parse and safer to modify.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔌 Plug and play
&lt;/h2&gt;

&lt;p&gt;You should favor technologies that are easy to get up and running. On a small team without a dedicated devops team,&lt;br&gt;
engineers should not be dedicating time to setup complex and/or custom infrastructure. There are two things you should&lt;br&gt;
be on the lookout for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A large community/ecosystem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If it even needs to be said, open-source is your friend. High numbers of users (downloads) and contributors are a good signal&lt;br&gt;
of a healthy ecosystem for an open-source library, meaning bugs should be resolved fairly quickly and new features will likely be shipped. A few quick (and imprecise) measures we use to quickly get a read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The number of stars a repo has on GitHub&lt;/li&gt;
&lt;li&gt;The date of the last significant update&lt;/li&gt;
&lt;li&gt;The number of outstanding (and/or stale) issues and pull requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;"Setup-free"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This one is fairly self-explanatory. I provide the quotations because I've yet to find a library that is truly and literally setup-free for anything beyond the most basic use case.&lt;br&gt;
However, it is still good to aim for libraries that will not require a large amount of someone's time to configure.&lt;/p&gt;

&lt;p&gt;None of this means, however, that you should always avoid the route that requires a little more work initially if it pays off in the long run. We'll talk more about that below in "Automate everything".&lt;/p&gt;

&lt;h5&gt;
  
  
  🙌 Our Recommendations
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://rubyonrails.org/"&gt;Rails&lt;/a&gt;&lt;/strong&gt; — One of our picks again. The long history and large community around Rails
means you can find a gem for practically anything your application could need to support. Many of them are even included in Rails itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt;&lt;/strong&gt; — Next brings a Rails-like simplicity to handling an isomorphic React application. Data fetching and page
definitions are unified, while you get to continue using any React patterns or components you prefer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔄 Aim for reusability
&lt;/h2&gt;

&lt;p&gt;Every time you re-use a block of code, you're saving some fraction of the time originally spent writing and testing it. It also reduces the surface&lt;br&gt;
area for bugs and for engineers to learn about in your codebase. Here are some ways to aim for reusability across your application's codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Share languages or frameworks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sharing a single language or framework across multiple parts of your system can increase the options for code sharing. JavaScript is often the&lt;br&gt;
top choice in this sense, as it can be used for everything from your database ORM to your native mobile client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build modularly&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When multiple parts of your system are using the same core language or framework, it becomes increasingly advantageous to build modularly.&lt;br&gt;
While this always helps with separation of concerns and unit testing, it can now open up new ways to share and re-use code across your application.&lt;/p&gt;

&lt;p&gt;For example, maintaining and publishing all of your app state across multiple clients becomes much more manageable when you can use&lt;br&gt;
the exact same Redux code across web, iOS, and Android.&lt;/p&gt;

&lt;p&gt;There are limits to reusability, however, as complexity often grows exponentially in the same direction. It is important to keep that in mind&lt;br&gt;
as you still aim to achieve the first goal we discussed, "easy to understand".&lt;/p&gt;

&lt;h5&gt;
  
  
  🙌 Our Recommendations
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://reactjs.org"&gt;React&lt;/a&gt;&lt;/strong&gt; — React has become an incredibly popular framework choice to go alongside a universal JavaScript application.
Many libraries exist to support code re-use across frameworks and platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://facebook.github.io/react-native/"&gt;React Native&lt;/a&gt;&lt;/strong&gt; — RN is one of those platforms that encourages code re-use with other React applications.
Write your native mobile applications (iOS, Android) using your same modules in familiar architectures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://reactjs.org"&gt;Gatsby&lt;/a&gt;&lt;/strong&gt; — Another React-related library, Gatsby allows for easy creation and publishing of static websites
using the same React files and patterns. Great for maintaining a scalable marketing site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/lerna/lerna"&gt;Lerna&lt;/a&gt;&lt;/strong&gt; — Lerna helps to more easily maintain a monorepo of inter-dependent JS packages. Easily
bump while keeping versions in sync, as well as publish to NPM.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🛠 Automate everything
&lt;/h2&gt;

&lt;p&gt;As we discussed above, complex systems and configurations should be avoided whenever possible. However, there's one exception: automation.&lt;/p&gt;

&lt;p&gt;Likely the place best suited for automation is your &lt;a href="https://www.atlassian.com/continuous-delivery/principles/continuous-integration-vs-delivery-vs-deployment"&gt;CI/CD process&lt;/a&gt;.&lt;br&gt;
This should include everything from linting, to testing, to production deployment. Ideally it's happening quite often, but it can also include any&lt;br&gt;
number of steps each of varying complexity.&lt;/p&gt;

&lt;p&gt;Automating any task is going to require a varying amount of upfront effort. However, the ongoing time saved is a multiple of&lt;br&gt;
the frequency and duration of these tasks. Additionally, the ongoing bugs and confusion that would arise from doing every process manually&lt;br&gt;
are immeasurable.&lt;/p&gt;

&lt;h5&gt;
  
  
  🙌 Our Recommendations
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.terraform.io/"&gt;Terraform&lt;/a&gt;&lt;/strong&gt; — Terraform helps us to fully manage our cloud infrastructure in source-controlled and peer-reviewed code.
This allows for consistency and self-documentation, while giving us much more confidence in our deployed systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt;&lt;/strong&gt; — Docker allows us to pre-define our various service environments (we have around 12) as reproducible
and deployable containers. Again this allows for consistency, self-documentation, and confidence in our systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ❗️ Are you a software engineer?
&lt;/h2&gt;

&lt;p&gt;At Monolist, we're following these tips to rapidly ship new features that help software engineers be their most productive. If you want to try it for free, just &lt;a href="https://app.monolist.co/login"&gt;click here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>startup</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
