<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mike Mwanje</title>
    <description>The latest articles on DEV Community by Mike Mwanje (@mwanjemike).</description>
    <link>https://dev.to/mwanjemike</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%2F625423%2Fe57c7dd5-5a9b-480e-bb7f-5f2e124e0b22.jpeg</url>
      <title>DEV Community: Mike Mwanje</title>
      <link>https://dev.to/mwanjemike</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mwanjemike"/>
    <language>en</language>
    <item>
      <title>Configure a MongoDB cluster with Ansible</title>
      <dc:creator>Mike Mwanje</dc:creator>
      <pubDate>Sat, 13 Jan 2024 08:39:00 +0000</pubDate>
      <link>https://dev.to/mwanjemike/configure-a-mongodb-cluster-with-ansible-55f1</link>
      <guid>https://dev.to/mwanjemike/configure-a-mongodb-cluster-with-ansible-55f1</guid>
      <description>&lt;p&gt;Manual server configuration is always a nightmare, especially when there is a large fleet of servers to manage. However, with Infrastructure as Code(IaC) automation tools like Ansible, all that pain goes away. In this blog, we shall leverage the automation tool to create a MongoDB sharded cluster from scratch in just a few minutes, using &lt;a href="https://github.com/123MwanjeMike/ansible-mongodb" rel="noopener noreferrer"&gt;this Ansible role&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Ansible installed on your control node&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Seven (or more) target servers running any Ubuntu OS version&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SSH access to the target servers&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Getting our hands dirty&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install the Ansible Role
&lt;/h3&gt;

&lt;p&gt;As I mentioned earlier, we shall be using an existing Ansible role and so we won't have to write all the playbooks by ourselves from scratch. However, to install the role, we shall need a file that has it's installation information. A &lt;em&gt;requirements.yml&lt;/em&gt; file . Run the commands below to create the file and the directory, &lt;em&gt;ansible_mongodb,&lt;/em&gt; which we shall be working.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/ansible_mongodb
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"-   name: 123mwanjemike.ansible_mongodb
    src: https://github.com/123MwanjeMike/ansible-mongodb
    version: v1.0.4"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/ansible_mongodb/requirements.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;em&gt; The latest version of the role, 123mwanjemike.ansible_mongodb, at the time of writing this blog, was v1.0.4. There may however be more recent &lt;/em&gt;&lt;a rel="noopener noreferrer nofollow" href="https://github.com/123MwanjeMike/ansible-mongodb/releases"&gt;&lt;em&gt;release versions&lt;/em&gt;&lt;/a&gt;&lt;em&gt; at the time you read this which are more recommended to use instead.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can now install the role using the Ansible Galaxy command below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-galaxy &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ~/ansible_mongodb/requirements.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fey9lld0geg2wpecv97vj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fey9lld0geg2wpecv97vj.png" alt="Installing the ansible role" width="800" height="75"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The Inventory File
&lt;/h3&gt;

&lt;p&gt;Create an inventory using the command below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/ansible_mongodb/inventory
&lt;span class="nb"&gt;touch&lt;/span&gt; ~/ansible_mongodb/inventory/hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, populate the inventory with the target servers grouped according to the respective replicaSets. That is to say, the Config servers replicaSet servers in their group, and the Shard servers in the corresponding group.&lt;/p&gt;

&lt;p&gt;Also, make sure to use the Fully Qualified Domain Names(FQDN) of the servers when adding them to the inventory. Below is how my inventory looks like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;            
    &lt;span class="na"&gt;mongo_cluster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;mongos_routers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;mongos-router-0.europe-north1-b.c.oceanic-muse-408212.internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;config_servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;mongod-cfgsvr-0.europe-north1-b.c.oceanic-muse-408212.internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;mongod-cfgsvr-1.europe-north1-c.c.oceanic-muse-408212.internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;mongod-cfgsvr-2.europe-north1-a.c.oceanic-muse-408212.internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;shard_0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;mongod-shard-0-0.europe-north1-b.c.oceanic-muse-408212.internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;mongod-shard-0-1.europe-north1-c.c.oceanic-muse-408212.internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;mongod-shard-0-2.europe-north1-a.c.oceanic-muse-408212.internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Variables
&lt;/h3&gt;

&lt;p&gt;There will be a number of variables at play when using this role. Let's get them right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Host Variables&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Run the commands below to put them in place&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;All hosts&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/ansible_mongodb/inventory/group_vars
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ansible_python_interpreter: /usr/bin/python3"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/ansible_mongodb/inventory/group_vars/all.yml
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Router(s)&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"cluster_role: 'router'"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/ansible_mongodb/inventory/group_vars/mongos_routers.yml
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Config Servers&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"cluster_role: 'config'

replica_set:
  name: 'cfgsvr'
  group: 'config_servers'"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/ansible_mongodb/inventory/group_vars/config_servers.yml
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Shard_0 Servers&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"cluster_role: 'shard'

replica_set:
  name: 'shard-0'
  group: 'shard_0'"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/ansible_mongodb/inventory/group_vars/shard_0.yml
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Configuration Variables&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can find &lt;a href="https://github.com/123MwanjeMike/ansible-mongodb/blob/develop/vars/main.yaml" rel="noopener noreferrer"&gt;the role's preset configuration variables&lt;/a&gt; in the &lt;code&gt;var/main.yml&lt;/code&gt; file at the path where the role was installed. For example in my case, it was installed at &lt;code&gt;~/.ansible/roles/123mwanjemike.ansible_mongodb&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;You can change them as you wish, especially the &lt;code&gt;mongo_configs&lt;/code&gt; versions and public keys given that they point to Ubuntu Jammy's MongoDB version 7.0 public keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cluster Variables&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As for the MongoDB cluster configuration variables, they have been documented on the role's README on GitHub &lt;a href="https://github.com/123MwanjeMike/ansible-mongodb#flags-and-variables" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Playbook
&lt;/h3&gt;

&lt;p&gt;We're almost good to go. We now just need a playbook to install and configure the MongoDB sharded cluster. I build from the &lt;a href="https://github.com/123MwanjeMike/ansible-mongodb#sample-playbook" rel="noopener noreferrer"&gt;sample playbook&lt;/a&gt; provided in the README of the role on GitHub. Below is my &lt;code&gt;~/ansible_mongodb/sample_playbook.yml&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongo_cluster&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;adminUser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;myAdminUser"&lt;/span&gt;
    &lt;span class="na"&gt;adminPass&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# Secret password here&lt;/span&gt;
    &lt;span class="na"&gt;new_shard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shard-0"&lt;/span&gt;
      &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shard_0"&lt;/span&gt;
    &lt;span class="na"&gt;tgt_db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;myDatabase"&lt;/span&gt;
    &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;readWrite"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;userAdmin"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;userName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;targetUser"&lt;/span&gt;
    &lt;span class="na"&gt;userPass&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# Secret password here&lt;/span&gt;
    &lt;span class="na"&gt;keyfile_src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./keyfile"&lt;/span&gt;
    &lt;span class="na"&gt;cfg_server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cfgsvr"&lt;/span&gt;
      &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config_servers"&lt;/span&gt;

  &lt;span class="na"&gt;pre_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate random string with OpenSSL on ansible controller&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openssl rand -base64 756 &amp;gt; keyfile&lt;/span&gt;
      &lt;span class="na"&gt;delegate_to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;creates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keyfile&lt;/span&gt;

  &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;123mwanjemike.ansible_mongodb&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;install_mongo"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;123mwanjemike.ansible_mongodb&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configure_mongo"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;123mwanjemike.ansible_mongodb&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;clear_logs"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;123mwanjemike.ansible_mongodb&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prepare_members"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;123mwanjemike.ansible_mongodb&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start_mongo"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;123mwanjemike.ansible_mongodb&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;init_replica"&lt;/span&gt;&lt;span class="pi"&gt;],&lt;/span&gt;
        &lt;span class="nv"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;cluster_role != 'router'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
      &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;123mwanjemike.ansible_mongodb&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create_admin"&lt;/span&gt;&lt;span class="pi"&gt;],&lt;/span&gt;
        &lt;span class="nv"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;cluster_role != 'router'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;delegate_to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;groups[replica_set.group]&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;first&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
      &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;123mwanjemike.ansible_mongodb&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;add_shard"&lt;/span&gt;&lt;span class="pi"&gt;],&lt;/span&gt;
        &lt;span class="nv"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;cluster_role == 'router'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;run_once&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
      &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;123mwanjemike.ansible_mongodb&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create_database"&lt;/span&gt;&lt;span class="pi"&gt;],&lt;/span&gt;
        &lt;span class="nv"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;cluster_role == 'router'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;run_once&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
      &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;The moment of truth has now come. We shall be running the playbook with the command below and see what happens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/ansible_mongodb/
ansible-playbook sample_playbook.yml &lt;span class="nt"&gt;-i&lt;/span&gt; inventory/hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;/p&gt;

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

&lt;p&gt;Checking out the cluster 🎉💃🏽🕺🏽😎&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Closing Remarks
&lt;/h2&gt;

&lt;p&gt;With this Ansible role, we've been able to create and configure a MongoDB sharded cluster in under 10 minutes without writing any single configuration for the servers by ourselves.&lt;/p&gt;

&lt;p&gt;Incase you were wondering, I am indeed the author of the role we have just used to create the cluster. I am also working on &lt;a href="https://github.com/123MwanjeMike/mongo-cluster-terraform" rel="noopener noreferrer"&gt;this Terraform project&lt;/a&gt; that can be used to create the server instances for the sharded cluster used herein. Currently, I have only set up the provider for Google Cloud Platform(GCP) but will be adding Azure, AWS, and other service providers soon. I hope you enjoy them as much as I did when creating them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cheers!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional resources
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.ansible.com/" rel="noopener noreferrer"&gt;Ansible&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html#roles" rel="noopener noreferrer"&gt;[Ansible] Roles&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.ssl.com/faqs/what-is-a-fully-qualified-domain-name/" rel="noopener noreferrer"&gt;What Is a “Fully Qualified Domain Name”?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.mongodb.com/docs/manual/core/sharded-cluster-components/#sharded-cluster-components" rel="noopener noreferrer"&gt;[MongoDB] Sharded Cluster Components&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.cloudflare.com/learning/cloud/what-is-a-virtual-private-cloud/" rel="noopener noreferrer"&gt;What is a virtual private cloud (VPC)?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>mongodb</category>
      <category>ansible</category>
      <category>devops</category>
    </item>
    <item>
      <title>My experience with GitOps. So far...</title>
      <dc:creator>Mike Mwanje</dc:creator>
      <pubDate>Sat, 26 Nov 2022 12:57:10 +0000</pubDate>
      <link>https://dev.to/mwanjemike/my-experience-with-gitops-so-far-4cnb</link>
      <guid>https://dev.to/mwanjemike/my-experience-with-gitops-so-far-4cnb</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;GitOps is a modern way to manage cloud-native systems that are powered by Kubernetes. It leverages a policy-as-code approach to define and manage every layer of the modern application stack - infrastructure, networking, application code, and the GitOps pipeline itself.&lt;br&gt;&lt;br&gt;
-- &lt;a href="https://www.weave.works/technologies/gitops/#what-is-gitops" rel="noopener noreferrer"&gt;Weaveworks&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GitOps as a concept was established in 2017 by Weaveworks and has since been widely adopted in software delivery with a growing number of CNCF tools being developed around it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgf46i1lpxv9abytecrc.png" alt="Example GitOps pipeline" width="800" height="405"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Source: &lt;a href="https://www.weave.works/blog/gitops-high-velocity-cicd-for-kubernetes" rel="noopener noreferrer"&gt;GitOps: High velocity CICD for Kubernetes&lt;/a&gt;&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;While at &lt;a href="https://airqo.net/" rel="noopener noreferrer"&gt;AirQo&lt;/a&gt;, I've had the opportunity of working in both traditional and GitOps environments and in this article, I share experience from a DevOps perspective after having made the shift in our delivery pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some bit of context
&lt;/h3&gt;

&lt;p&gt;AirQo is a cleantech startup whose vision is &lt;strong&gt;clean air for all African cities&lt;/strong&gt;. A key duty in getting there is availing our users with Africa's air quality information and so we have a number software products ranging from mobile applications, web applications, and an API just for that. Our API relies on a couple of microservices in the backend to transform the Particulate Matter(PM) data collected by our air quality monitors into consumption-ready information. We run our microservices on some few Kubernetes(k8s) clusters.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffa8z0r4tldpb9gtitv33.png" alt="AirQo Architecture diagram" width="800" height="494"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Architectural diagram&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We use a Gitflow worflow in &lt;a href="https://github.com/airqo-platform/AirQo-frontend" rel="noopener noreferrer"&gt;our mono-repository&lt;/a&gt; with all the products and the corresponding k8s manifests in the same repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial setup
&lt;/h3&gt;

&lt;p&gt;In the early days, our continuous delivery pipeline had both the integration and deployment done by the same tool, GitHub Actions. All application images pointed to the "latest" tag in within k8s configurations thus it was the de facto for all applications restarts. GitHub Actions built and pushed the images to Google Container Registry(GCR), but was did not update the corresponding k8s manifest with the new image tag despite the fact that it was the uniquely identifiable image tag(e.g prod-docs-123abc). This meant that the deployed state was always divergent from the state on GitHub at the image tag level at least. Some of the challenges we faced include;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Difficulty in telling the actual resource definition of applications on the cluster.
There were times when quick/minor fixes were made to applications directly through kubectl without going through the git workflow. It was difficult for the rest of the team to know of such changes given that they were rarely communicated or committed to GitHub. Not to mention that the image tag was obviously one of such application properties we weren't sure about.&lt;/li&gt;
&lt;li&gt;The development team lacked visibility into their applications.
Most development team members weren't conversant with k8s and kubectl commands to for instance view live application logs whenever need arose. This made troubleshooting unnecessarily a step longer given since a DevOps engineer was needed to retrieve the logs and thus,&lt;/li&gt;
&lt;li&gt;A larger workload for the operations team given that it was an operations-centric approach.&lt;/li&gt;
&lt;li&gt;Operations engineers always had to have access to the clusters via a kubeconfig in order to carry out even very basic tasks.&lt;/li&gt;
&lt;li&gt;Rolling back changes was more technical  than necessary. Given that we defaulted to the latest tag on GitHub. A DevOps engineer had to get the image tag of a previous application version from GCR, then run a &lt;code&gt;kubectl patch&lt;/code&gt; command to update the application image tag with a previous one.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  How we got there
&lt;/h3&gt;

&lt;p&gt;A mono-repository setup can present some challenges and we faced one ourselves with the different products' delivery pipelines to production getting intertwined; deeming it impossible to deploy a specific product at a time. The CI/CD pipelines were all dependent on the git workflow and thus had the same trigger. Perhaps I can discuss how we went about in another article but during my search for an alternative autonomous trigger, is when I landed on ArgoCD. Up until then, I had miniature knowledge of GitOps but I found it interesting as I read about it. Fascinated by how the methodology would address many challenges we faced, I instantly &lt;a href="https://github.com/airqo-platform/AirQo-api/tree/staging/k8s/argo-cd" rel="noopener noreferrer"&gt;installed ArgoCD&lt;/a&gt; on one of our clusters. Due to it's multi-tenant nature, I was able to add setup applications in the different clusters and their respective projects under one roof: and subsequently on boarded the entire team onto it. &lt;br&gt;
We now use ArgoCD as our deploy operator but kept GitHub Actions as our CI tool and &lt;a href="https://github.com/airqo-platform/AirQo-frontend/blob/17cbaacfcff96997a45c50ae095cec4d32ab1fbd/.github/workflows/deploy-frontends-to-staging.yml#L396" rel="noopener noreferrer"&gt;Config Updater&lt;/a&gt; for image tags on Helm(which we defaulted to).&lt;/p&gt;

&lt;h3&gt;
  
  
  ArgoCD removed pain points
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;With ArgoCD, we are notified via slack whenever the state of the deployed application diverges from the state on GitHub. The states are also automatically synced to match the GitHub definition in line with the GitOps principle of Git as the source of truth. This ensures that changes are documented on GitHub as a bare minimum.&lt;/li&gt;
&lt;li&gt;The development team is now able to self serve on anything to do with their applications. The ArgoCD UI is so intuitive that they can carry out tasks ranging from creating new applications to accessing logs of existing ones without assistance from the operations team. A more developer-centric approach.&lt;/li&gt;
&lt;li&gt;Rollbacks are now much easier. It's now a matter of reverting changes on the GitHub repo and an older image tag will be deployed automatically.&lt;/li&gt;
&lt;li&gt;As the DevOps engineer, I am now able to run basic operations on the clusters without using and scripting commands. For instance, I can view health statuses of all applications on the clusters at the comfort of my phone, without running any commands to switch cluster context.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h3&gt;
  
  
  Final words
&lt;/h3&gt;

&lt;p&gt;Our adoption of GitOps is recent but I can definitely tell that there's a lot we have to benefit from it at AirQo as we continue to explore it. I thus generally believe that what I've shared here is only but the surface and trust that you and your organisation could discovered that this methodology solves challenges you had gotten accustomed when you try it.&lt;/p&gt;

&lt;p&gt;Until next time... &lt;em&gt;Happy hacking&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Further reading&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.weave.works/blog/the-history-of-gitops" rel="noopener noreferrer"&gt;The History of GitOps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.weave.works/blog/what-is-gitops-really" rel="noopener noreferrer"&gt;What is GitOps really&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.weave.works/technologies/gitops/#key-benefits-of-gitops" rel="noopener noreferrer"&gt;Key benefits of GitOps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.gitops.tech/" rel="noopener noreferrer"&gt;gitops.tech&lt;/a&gt; &lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>emptystring</category>
    </item>
    <item>
      <title>Creating a new website? I have Nuttertools for you.</title>
      <dc:creator>Mike Mwanje</dc:creator>
      <pubDate>Sat, 03 Sep 2022 09:15:57 +0000</pubDate>
      <link>https://dev.to/mwanjemike/creating-a-new-website-i-have-nuttertools-for-you-458h</link>
      <guid>https://dev.to/mwanjemike/creating-a-new-website-i-have-nuttertools-for-you-458h</guid>
      <description>&lt;p&gt;Ever played Grand Theft Auto? If you have, then you might be familiar with "Nuttertools". Nuttertools is one of the many cheat codes in the video game which gives your character a set of weapons. In this article, I'll share some cheat codes of my own with you. A special group of websites that will make you stand apart from any ordinary web developer. I call these my Nuttertools.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;a href="https://www.webpagetest.org" rel="noopener noreferrer"&gt;webpagetest.org&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgvth4xha0jmby1bl41pg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgvth4xha0jmby1bl41pg.png" alt=" " width="800" height="384"&gt;&lt;/a&gt;&lt;br&gt;
With this website, you get to run performance, lighthouse, core web vitals, visual comparison, and trace route tests on your website. It can simulate a wide range of internet connections right from 2G to cable in various locations around the world. The tests are recorded and so can can playback to see how your website gets displayed on the browser and also share the recorded results if you wish to.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;a href="https://validator.w3.org" rel="noopener noreferrer"&gt;validator.w3.org&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0pjgbe40myfhdxew3gl0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0pjgbe40myfhdxew3gl0.png" alt=" " width="800" height="383"&gt;&lt;/a&gt;&lt;br&gt;
This W3C markup validator checks for areas of improvement and errors in your website HTML syntax and suggests how these can be addressed. As you can see in the image above, I have some of my own I can fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. &lt;a href="https://wave.webaim.org" rel="noopener noreferrer"&gt;wave.webaim.org&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk6tjr7jtn2fy4pmebq08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk6tjr7jtn2fy4pmebq08.png" alt=" " width="800" height="384"&gt;&lt;/a&gt;&lt;br&gt;
The internet has a very large number of users who are diverse in many of ways. This tool comes in handy if you wish to create a website that is accessible to a wide audience, including those with visual, audial, physical, and cognitive impairments. It helps you identify implementations that may not be favourable for impaired individuals and how these can be can corrected. An example is colour contrasts that may not be distinguishable by people with colour blindness as well as they would be for normal users. This way you can promote inclusivity even in your web solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. &lt;a href="https://www.whatsmydns.net" rel="noopener noreferrer"&gt;whatsmydns.net&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkytqhtdnn1m2cxw7vv8o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkytqhtdnn1m2cxw7vv8o.png" alt=" " width="800" height="387"&gt;&lt;/a&gt;&lt;br&gt;
Let's say you've gotten a new domain and/or made a couple of changes. These changes can take a some good time, up to 72hours  to reflect worldwide on the all the Domain Name Servers. This website helps you check the DNS propagation.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. &lt;a href="https://www.websitecarbon.com" rel="noopener noreferrer"&gt;websitecarbon.com&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4m99jz53dpi3yctitxfh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4m99jz53dpi3yctitxfh.png" alt=" " width="800" height="383"&gt;&lt;/a&gt;&lt;br&gt;
Last but most definitely not the least, is a tool that calculates how much carbon is released into the atmosphere whenever a user visits your website. You can then see how much your website contributes to global warming and also suggests how you can reduce the carbon footprint. If there's at least one take away from this article, I hope this be it.&lt;/p&gt;

&lt;p&gt;Do you know of any other "Nuttertools" that aren't on this list, do share in the comments section. I'd love to learn from you too.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cheers!&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Set up Kubernetes role based access control</title>
      <dc:creator>Mike Mwanje</dc:creator>
      <pubDate>Wed, 24 Aug 2022 20:03:07 +0000</pubDate>
      <link>https://dev.to/mwanjemike/set-up-kubernetes-role-based-access-control-2418</link>
      <guid>https://dev.to/mwanjemike/set-up-kubernetes-role-based-access-control-2418</guid>
      <description>&lt;p&gt;Kubernetes(K8s) role-based access control is a powerful tool in restricting access to resources within a Kubernetes cluster. In this post, we shall briefly discuss what role-based access control is, and how to set it up in Kubernetes. I promise, it's not as long a process as you may think.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is role-based access control?
&lt;/h2&gt;

&lt;p&gt;Role-based access control(RBAC) is a security model in which users are assigned roles, and can then be granted access to certain resources based on those roles. For example, an organisation can have a role-based access control system that allows DevOps engineers to perform privileged operations within the entire Kubernetes cluster, while restricting the application developers to only viewing information about their applications deployed in a given namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Definitions
&lt;/h3&gt;

&lt;p&gt;Before setting up RBAC in Kubernetes, let's review some of the key concepts that we are going to be dealing with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service accounts&lt;/strong&gt; are identities that can be used to delegate access to Kubernetes resources within a namespace. They can be used by users or services. We shall do some minor tweaks later on in the article using clusterrolebindings to enable us override the "namespacedness" limitation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resources&lt;/strong&gt; are objects within a Kubernetes cluster that we aim to implement role-based access control around. Examples include pods, deployments, replicasets, configmaps, and services. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Roles&lt;/strong&gt; are collections of permissions that are applied within a given namespace. In most cases, these consist of a set of permissions that can be granted to service accounts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rolebindings&lt;/strong&gt; are the association of a role with a service account. In other words, rolebindings are used to assign roles. Think of a rolebinding as a mechanism to plug a set of permissions(a &lt;em&gt;role&lt;/em&gt;) to a service account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clusterroles&lt;/strong&gt; are roles that can be assigned to service accounts within a Kubernetes cluster. Whereas roles are namespaced, clusterroles apply to the entire cluster. Some default ones in a k8s cluster include view, edit, admin, and cluster-admin.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clusterrolebindings&lt;/strong&gt; are associations of a clusterrole with a service account. As you might have guessed, clusterrolebindings enable us assign cluster wide permissions to a service accounts through clusterroles.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let us see how we can set up RBAC in Kubernetes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to set up a Kubernetes cluster using RBAC
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A working Kubernetes cluster. If you don't have one yet, you might find &lt;a href="https://itnext.io/kubernetes-installation-methods-the-complete-guide-1036c860a2b3" rel="noopener noreferrer"&gt;this post&lt;/a&gt; helpful. It lists a couple of ways you could setup a K8s cluster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kubectl. You can install kubectl following &lt;a href="https://kubernetes.io/docs/tasks/tools/#kubectl" rel="noopener noreferrer"&gt;this guide&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kubernetes 1.20 or later. Earlier versions of Kubernetes are no longer actively maintained and RBAC in those versions is not as stable. &lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/" rel="noopener noreferrer"&gt;This documentation&lt;/a&gt; can guide you through the process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/" rel="noopener noreferrer"&gt;The Kubernetes Dashboard&lt;/a&gt; if you intend on using it in the Access the Kubernetes dashboard section later on.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setting up RBAC within a namespace
&lt;/h3&gt;

&lt;p&gt;First, let us create a namespace which we are going to be working with. Let's call it &lt;strong&gt;&lt;em&gt;dev-env&lt;/em&gt;&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create namespace dev-env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;Output:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftygmkd6ri2zypt4sopue.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftygmkd6ri2zypt4sopue.png" alt="Output of  kubectl create command namespace" width="800" height="38"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, create a service account called &lt;strong&gt;&lt;em&gt;app-dev&lt;/em&gt;&lt;/strong&gt; which we shall use all through out.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create serviceaccount app-dev --namespace dev-env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;Output:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1n4bk5jvxhil1j8cak6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1n4bk5jvxhil1j8cak6.png" alt="Output of  kubectl create command serviceaccount" width="800" height="39"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We shall follow this up by creating an admin role that has all rights within the &lt;code&gt;dev-env&lt;/code&gt; namespace. Below is its manifest, which is also available &lt;a href="https://github.com/123MwanjeMike/k8s-rbac/blob/main/admin-role.yaml" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: admin
  namespace: dev-env
rules:
- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["*"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Apply the file to create the &lt;strong&gt;&lt;em&gt;admin&lt;/em&gt;&lt;/strong&gt; role.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f https://raw.githubusercontent.com/123MwanjeMike/k8s-rbac/main/admin-role.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;Output:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkgx8l9637rfux6s2esv2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkgx8l9637rfux6s2esv2.png" alt="Output after creating role" width="800" height="36"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;We shall be creating the rest of the Kubernetes resources in this tutorial from the pre-written manifest files in &lt;a href="https://github.com/123MwanjeMike/k8s-rbac" rel="noopener noreferrer"&gt;this repository&lt;/a&gt; as we have done above. However, you may also edit these manifests if you wish to create K8s resources tailored to your needs.&lt;/em&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/123MwanjeMike" rel="noopener noreferrer"&gt;
        123MwanjeMike
      &lt;/a&gt; / &lt;a href="https://github.com/123MwanjeMike/k8s-rbac" rel="noopener noreferrer"&gt;
        k8s-rbac
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Setting up Kubernetes role based access control tutorial manifests
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;k8s-rbac&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;A list of Kubernetes resources for setting up of role-based access control in the article &lt;a href="https://blog.mikemwanje.dev/set-up-kubernetes-role-based-access-control" rel="nofollow noopener noreferrer"&gt;Set up Kubernetes role based access control&lt;/a&gt;&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/123MwanjeMike/k8s-rbac" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Next,create a rolebinding, &lt;strong&gt;&lt;em&gt;app-dev-rolebinding&lt;/em&gt;&lt;/strong&gt;, of the &lt;code&gt;admin&lt;/code&gt; role to the &lt;code&gt;app-dev&lt;/code&gt; service account. Below is the manifest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: app-dev-rolebinding
  namespace: dev-env
subjects:
- kind: ServiceAccount
  name: app-dev
  namespace: dev-env
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the command below to create it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f https://raw.githubusercontent.com/123MwanjeMike/k8s-rbac/main/rolebinding.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Output:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhetgpowuqzlq4a1nx248.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhetgpowuqzlq4a1nx248.png" alt="Output of creating a rolebinding" width="800" height="28"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Our service account is now an admin within the &lt;code&gt;dev-env&lt;/code&gt; namespace. We can even go further and define access rights for it across the entire cluster. Let's say, basing on our DevOps engineer and application developer example we had at the start, that we want our application developers to view any resource cluster wide and even to use the Kubernetes dashboard. How we can we do this?&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting up RBAC for the entire cluster
&lt;/h3&gt;

&lt;p&gt;For the first part, we're in luck! Kubernetes has a default view clusterrole which can be used for viewing of all resources in a cluster; and so we shall just leverage this. We shall create a clusterrolebinding, &lt;code&gt;app-dev-view&lt;/code&gt;, that does just that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: app-dev-view
subjects:
- kind: ServiceAccount
  name: app-dev
  namespace: dev-env
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: view
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the clusterrolebinding&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f https://raw.githubusercontent.com/123MwanjeMike/k8s-rbac/main/clusterrolebindings/view.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Now, for &lt;code&gt;app-dev&lt;/code&gt; to access the Kubernetes dashboard, we could easily just create a bind the &lt;code&gt;edit&lt;/code&gt; default clusterrole to it since it has the access rights needed for this. However, this would also give the service account more permissions than it should have and so we want to use a more fine-grained clusterrole in accordance to the principle of least privileges.&lt;/p&gt;

&lt;p&gt;So, we shall create a custom clusterrole with only the required permissions needed to access the K8s dashboard on top of those already held by the service account through default &lt;code&gt;view&lt;/code&gt; clusterrole.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: view-addition-for-k8s-dashboard-access
rules:
- apiGroups: [""]
  resources: ["pods/attach", "pods/exec", "pods/portforward", "pods/proxy", "services/proxy"]
  verbs: ["get", "list", "watch", "create"]
- apiGroups: [""]
  resources: ["services"]
  verbs: ["proxy"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the command below to create the &lt;code&gt;view-addition-for-k8s-dashboard-access&lt;/code&gt; clusterrole with the above definition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f https://raw.githubusercontent.com/123MwanjeMike/k8s-rbac/main/custom-clusterrole.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;We can now bind &lt;code&gt;view-addition-for-k8s-dashboard-access&lt;/code&gt; clusterrole to the &lt;code&gt;app-dev&lt;/code&gt; service account for it access the K8s dashboard. Let's have a look at the clusterrolebinding manifest for this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: app-dev-use-k8s-dashboard
subjects:
- kind: ServiceAccount
  name: app-dev
  namespace: dev-env
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: view-addition-for-k8s-dashboard-access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the &lt;code&gt;app-dev-use-k8s-dashboard&lt;/code&gt; clusterrolebinding&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f https://raw.githubusercontent.com/123MwanjeMike/k8s-rbac/main/clusterrolebindings/use-k8s-dashboard.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Our &lt;code&gt;app-dev&lt;/code&gt; service account can now be used by our "application developers" to create applications in the &lt;code&gt;dev-env&lt;/code&gt; namespace and conveniently access the k8s dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it to use
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create a kubectl configuration
&lt;/h3&gt;

&lt;p&gt;We are going to need a token for the &lt;code&gt;app-dev&lt;/code&gt; service account and so to get it, run the commands below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export NAMESPACE="dev-env"
export SERVICE_ACCOUNT="app-dev"
kubectl -n ${NAMESPACE} describe secret $(kubectl -n ${NAMESPACE} get secret | (grep ${SERVICE_ACCOUNT} || echo "$_") | awk '{print $1}') | grep token: | awk '{print $2}'\n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output will be some long string. It should start with something similar to what I have below. Save that &lt;strong&gt;token&lt;/strong&gt; somewhere temporarily.&lt;/p&gt;

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

&lt;p&gt;Next, get the certificate data by running the command below in the same terminal where you had defined the &lt;em&gt;NAMESPACE&lt;/em&gt; and &lt;em&gt;SERVICE_ACCOUNT&lt;/em&gt; variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl  -n ${NAMESPACE} get secret `kubectl -n ${NAMESPACE} get secret | (grep ${SERVICE_ACCOUNT} || echo "$_") | awk '{print $1}'` -o "jsonpath={.data['ca\.crt']}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output will be another long string whose beginning is something similar to what I have below. Save that &lt;strong&gt;certificate data&lt;/strong&gt; somewhere temporarily.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrv2wowhytik3l1ihknk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrv2wowhytik3l1ihknk.png" alt="image.png" width="654" height="23"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With these two, we let's create our kubectl config file. You might already have a *.kube/config *which you might still need. So, we shall back it up as .kube/config.bak and create a new one for the &lt;code&gt;app-dev&lt;/code&gt;. Run the commands below to do precisely that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd
mv .kube/config .kube/config.bak
nano .kube/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will open the nano text editor. Fill in the template below paste in the editor. Make sure to substitute the &lt;em&gt;certificate-data&lt;/em&gt;, &lt;em&gt;server-ip-address&lt;/em&gt;, &lt;em&gt;cluster-name&lt;/em&gt;, and &lt;em&gt;token&lt;/em&gt; with the correct values before saving the changes. Remember to delete the token and certificate data from where you had temporarily saved them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: v1
clusters:
- cluster:
    certificate-authority-data:  &amp;lt;certificate-data&amp;gt;
    server: https://&amp;lt;server-ip-address&amp;gt;:6443
  name: &amp;lt;cluster-name&amp;gt;
contexts:
- context:
    cluster: &amp;lt;cluster-name&amp;gt;
    namespace: dev-env
    user: app-dev
  name: &amp;lt;cluster-name&amp;gt;
current-context: dev-env
kind: Config
preferences: {}
users:
- name: app-dev
  user:
    client-key-data:  &amp;lt;certificate-data&amp;gt;
    token:  &amp;lt;token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Access the Kubernetes dashboard
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;kubectl proxy&lt;/code&gt; and click &lt;a href="http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/" rel="noopener noreferrer"&gt;here&lt;/a&gt; to access the dashboard.&lt;/li&gt;
&lt;li&gt;Sign in with the .kube/config we created in the previous step.&lt;/li&gt;
&lt;li&gt;Attempt to create a user role in the dev-env namespace with the manifest below. All should go on well.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-admin
  namespace: dev-env
rules:
- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["*"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4d46w7rsx19335fm9pk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4d46w7rsx19335fm9pk.png" alt="test-admin role creation successful in dev-env namespace" width="800" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Attempt to create the user role in the kube-public namespace with the manifest below.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: test-admin
  namespace: kube-public
rules:
- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["*"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tadaa!&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyt8nxe222afg9v7bmws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyt8nxe222afg9v7bmws.png" alt="test-admin role creation failed in kube-public namespace" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We did it. &lt;code&gt;app-dev&lt;/code&gt; has no such rights outside the &lt;code&gt;dev-env&lt;/code&gt; namespace. All application developers can now use that .kube/config file to gain controlled access to the cluster&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this post, we looked at how we can implement RBAC not only at the namespace level, but also at the cluster level. We specifically used a service account given that it is managed by Kubernetes taking away this task from us. It is also important to note that Kubernetes also user accounts as much as it does service accounts. One key difference between the two is that user accounts represent actual Kubernetes users unlike the service accounts.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this article and that you'll show support of some sort as I'd greatly appreciate that. And if you have any thoughts or questions in relation to this, feel free to drop them via the comments section. I'd love to hear from you.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Happy hacking&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional resources
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/" rel="noopener noreferrer"&gt;Using RBAC Authorization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kubernetes/kubernetes/blob/master/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml" rel="noopener noreferrer"&gt;Kubernetes default clusterroles definitions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege" rel="noopener noreferrer"&gt;The principle of least privileges&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>What is best platform for tech blogging and building a developer community?</title>
      <dc:creator>Mike Mwanje</dc:creator>
      <pubDate>Thu, 21 Jul 2022 21:58:00 +0000</pubDate>
      <link>https://dev.to/mwanjemike/what-is-best-platform-for-tech-blogging-and-building-a-developer-community-5ho2</link>
      <guid>https://dev.to/mwanjemike/what-is-best-platform-for-tech-blogging-and-building-a-developer-community-5ho2</guid>
      <description>&lt;p&gt;As a technology professional, I have always been passionate about sharing knowledge with others. With the rise of blogs and social media, I realized that there are many other benefits to blogging besides just sharing knowledge. For example, by blogging, I am able to build visibility and grow an audience. Additionally, documenting my work and experiences can be a valuable resource for myself in the future.&lt;/p&gt;

&lt;p&gt;Given that I am committed to blogging monthly, it was important for me to find the best platform for this. I wanted to take advantage of everything that blogging has to offer and this platform had to be feature rich and user-friendly. So I decided to carry out a research on different platforms that could assist me in achieving my goals. The platforms that I evaluated were Medium, DEV(Dev.to), and tech Twitter. You might be wondering about Hashnode but I will get to this in a bit.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you were to choose one platform to blog on, which one would it be and why?
&lt;/h3&gt;

&lt;p&gt;I picked thoughts from people both &lt;a href="https://twitter.com/Mwanje_Mike_/status/1540716014622543874?s=20&amp;amp;t=0j7VVSl8qdhm32tMnMC8Bw" rel="noopener noreferrer"&gt;on Twitter&lt;/a&gt; and a couple of developer communities, and here is what I found...&lt;/p&gt;

&lt;h4&gt;
  
  
  DEV
&lt;/h4&gt;

&lt;p&gt;DEV is a great place to start out as a technical blogger. It has a large technical audience and a supportive developer community. Articles from junior and senior developers get the same level of visibility. This makes it an ideal place for enthusiastic learners as well as seasoned professionals to share their learnings and expertise with a broader audience. DEV Markdown also makes it easy to write code snippets and have them formatted correctly.&lt;/p&gt;

&lt;h4&gt;
  
  
  Medium
&lt;/h4&gt;

&lt;p&gt;Medium provides a greater visibility for content than DEV with a much more diverse audience. While it is not as popular with developers, Medium has a growing number of tech bloggers who use the platform to share their work and insights with a wider audience. Medium is however best to use if you already have an established blog audience and want to amplify your content as new bloggers may not get as much visibility. Medium is a better place for sharing experiences and ideas rather than just technical content. It is also a great place to create a thought leadership presence for yourself and build a brand.&lt;/p&gt;

&lt;h4&gt;
  
  
  Hashnode
&lt;/h4&gt;

&lt;p&gt;Hashnode is relatively new to the market compared to other blogging platforms. However, it has risen quickly to become popular with developers because it offers all the features that tech bloggers require. I had initially not considered Hashnode as it is somewhat similar to DEV. Both are tailored specifically for the developer community and also have a similar markdown support (although Hashnode’s offerings are far superior to those available on DEV). Hashnode however has smaller audiences than DEV and Medium and as a result, you may not get as much attention.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tech Twitter
&lt;/h4&gt;

&lt;p&gt;Tech Twitter is where all the developers hang out. If you have not heard of it before, then you must be living under a rock! Tech Twitter is a micro blogging platform where developers share their latest discoveries, thoughts and insights on various tech topics. It is a noisy place where you need to be constantly talking to get noticed. However, it is a great place to build relationships with other developers and get your content read by the right people.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Choosing the right blog platform is not easy. It depends on your objective and how involved you want to be with the platform. However, if you are starting out and are looking to build an organic audience, then I would suggest DEV. If you are looking for a platform that provides greater visibility and a wider audience as a thought leader, then Medium the best choice for you. While if you want to take charge of your blogging process and build a more niche audience, then Hashnode would be the best platform for you. Finally, if you are looking for a platform to constantly keep your audience engaged, then Tech Twitter is the place to be.&lt;br&gt;
The good news is that you can always use canonical urls to republish your blogs to leverage what the other platforms have to offer.&lt;/p&gt;

&lt;p&gt;Do you have any thoughts on what I just shared? Let me know in the comments below! I would also love to hear from you on what your favourite blogging platform and why?&lt;/p&gt;

&lt;p&gt;Happy blogging!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to run API automated tests with Newman on CircleCI.</title>
      <dc:creator>Mike Mwanje</dc:creator>
      <pubDate>Sat, 15 Jan 2022 17:10:48 +0000</pubDate>
      <link>https://dev.to/mwanjemike/how-to-run-api-automated-tests-with-newman-on-circleci-2ppi</link>
      <guid>https://dev.to/mwanjemike/how-to-run-api-automated-tests-with-newman-on-circleci-2ppi</guid>
      <description>&lt;p&gt;Postman is a great tool not only for building, but also for testing APIs. In this post, we shall look at how to use Newman, Postman’s command-line collection runner, to run automated tests for an API in a CI/CD pipeline running on CicleCI. Whereas there maybe various tools and libraries to test APIs built in a specific language, automation testing with Newman cuts across and thus the steps followed herein can be followed to test an API developed in any programming language. That stated, we shall use a Node.js API in this post.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tutorial
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Setting up&lt;/li&gt;
&lt;li&gt;Importing collection and environment&lt;/li&gt;
&lt;li&gt;Integrating with CircleCI&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Setting up
&lt;/h3&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before we start, make sure you have the newman cli installed on your computer. If you do not have it yet, install it globally by simply running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm install -g newman
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Fork and clone &lt;a href="https://github.com/123MwanjeMike/node-express-realworld-example-app" rel="noopener noreferrer"&gt;this repository&lt;/a&gt; which has the API we shall be using and change directory into the created folder. Run the commands below to do so.to install it globally.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git clone https://github.com/123MwanjeMike/node-express-realworld-example-app.git
$ cd node-express-realworld-example-app/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Switch to the &lt;code&gt;00--tutorialStart&lt;/code&gt; branch.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git checkout 00--tutorialStart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Install the dependencies with a package manager(npm, yarn) of your choice. In my case, I’ll be using yarn all throughout.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ yarn install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Run the start script to confirm that everything is working fine.&lt;br&gt;
Ps: You need to have MongoDB installed on your machine. If you don’t have it, head over &lt;a href="https://docs.mongodb.com/guides/server/install/" rel="noopener noreferrer"&gt;here&lt;/a&gt; to install it or jump right to Importing collection and environment.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ yarn start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Open another terminal and run the test script&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ yarn test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ei33hrux0z08wxrqtdw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ei33hrux0z08wxrqtdw.png" alt="All tests passing" width="700" height="292"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Importing collection and environment
&lt;/h3&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let’s head over to Postman to get our hands dirty. We need to have our collection in Postman and its environment as well. So, open your postman Workspace and click the &lt;strong&gt;Import&lt;/strong&gt; button. You can either import from the test folder of your cloned repository or from your forked repository(&lt;code&gt;00--tutorialStart&lt;/code&gt; branch) on GitHub.&lt;/p&gt;

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

&lt;p&gt;With the import complete, set the environment to &lt;code&gt;Conduit API Tests — Environment&lt;/code&gt;. We can add one more test of our own. So head over to &lt;code&gt;Auth/Login and Remember Token&lt;/code&gt; in the &lt;em&gt;Conduit API Tests&lt;/em&gt; collection, select the &lt;strong&gt;Tests&lt;/strong&gt; tab, and add a test to check that the response status code is 200 from the &lt;strong&gt;SNIPPETS&lt;/strong&gt; on the right. Send the request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzc2aqoujzoqf442jjxgx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzc2aqoujzoqf442jjxgx.gif" alt="Adding a status code test from snippets" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Integrating with CircleCI
&lt;/h3&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We shall now integrate our API in Postman with CircleCI. This way, we shall also be able to trigger and view test builds from within Postman. So, head over to &lt;strong&gt;APIs&lt;/strong&gt; and click ‘+’ to create a new API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fic2non1tttm5ellp4ry2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fic2non1tttm5ellp4ry2.png" alt="Conduit API, version 1.0.0 created" width="700" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Head over to the &lt;strong&gt;Test&lt;/strong&gt; tab and click &lt;strong&gt;Add Test Suite&lt;/strong&gt;. Select &lt;strong&gt;Add existing test&lt;/strong&gt;, then select &lt;code&gt;Conduit API Tests&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;Still at the same page, under &lt;strong&gt;Connect to CI/CD Builds&lt;/strong&gt;, select CircleCI. You’ll be presented with a screen such as below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsl6er0z5377ln0lwb5it.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsl6er0z5377ln0lwb5it.png" alt="Connect to CircleCI pop up empty" width="479" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to generate an API key for this integration on CircleCI. So click &lt;a href="https://app.circleci.com/settings/user/tokens?return-to=https%3A%2F%2Fapp.circleci.com%2Fpipelines%2Fgithub%2FBSE21-13" rel="noopener noreferrer"&gt;here&lt;/a&gt; to go to your CircleCI &lt;strong&gt;User Settings&lt;/strong&gt; page, &lt;strong&gt;Personal Access Tokens&lt;/strong&gt; section from where you’ll create one. Copy the API token you have added and paste it in the &lt;strong&gt;API Key&lt;/strong&gt; input on the Postman screen above.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3y2xy284dlombhalg0s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3y2xy284dlombhalg0s.png" alt="Creating a CircleCI API token for Postman integration" width="519" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter a &lt;strong&gt;Nickname&lt;/strong&gt; and select the &lt;strong&gt;CI Project&lt;/strong&gt; to run the tests. It’s very likely your forked project is not on that list yet. You want to go back to your CircleCI dashboard and &lt;strong&gt;Set Up Project&lt;/strong&gt; for it to reflect on Postman. It’s okay for the first build to fail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi5o5ye0wtqvym8yvltj9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi5o5ye0wtqvym8yvltj9.png" alt="Connect to CircleCI pop up filled in" width="471" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next we shall generate a Newman configuration that we shall place in our &lt;code&gt;.circleci/config.yml&lt;/code&gt; file in the project repository. Click &lt;strong&gt;Configure Newman&lt;/strong&gt; in the top right corner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ao0zsejv3e1irayfzow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ao0zsejv3e1irayfzow.png" alt="Generate Newman Configuration screen" width="503" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For our environment, we need to create a mock server so that we don’t run our requests against any real data. So click &lt;strong&gt;Mock Servers&lt;/strong&gt; on the Postman left menu and click ‘&lt;strong&gt;+&lt;/strong&gt;’ to create one. Choose the &lt;strong&gt;Select an existing collection&lt;/strong&gt; option.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvh01913mizdxnir30g1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvh01913mizdxnir30g1.png" alt="Creating a Mock Server on Postman" width="700" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select the &lt;code&gt;Conduit API Tests&lt;/code&gt; collection. You’ll proceed to the Configuration screen as below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F28z5staf6ojny2goo082.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F28z5staf6ojny2goo082.png" alt="Configuration stage for the Mock Server creation process" width="700" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the &lt;strong&gt;Create Mock Server&lt;/strong&gt; button and copy the Mock URL provided.&lt;br&gt;
Go to &lt;strong&gt;Environments&lt;/strong&gt; to update the &lt;code&gt;apiUrl&lt;/code&gt; variable for the &lt;code&gt;Conduit API Tests — Environment&lt;/code&gt;. Set its &lt;strong&gt;INITIAL VALUE&lt;/strong&gt; to the Mock URL you copied and save the changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwjigdmfjwh0waet32e3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwjigdmfjwh0waet32e3.png" alt="Conduit APT Tests -Environment variables" width="700" height="173"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With everything now set, we can head back to generate our Newman configuration. Set the collection to &lt;code&gt;Conduit APT Tests&lt;/code&gt; and environment to &lt;code&gt;Conduit APT Tests -Environment&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9zlh5aw76czmykny61rs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9zlh5aw76czmykny61rs.png" alt="Newman configuration for CircleCI generated" width="503" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the generated configuration. We shall add it to &lt;code&gt;.circleci/config.yml&lt;/code&gt; in our code base. So create the file in the project directory with the commands below.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mkdir .circleci
$ touch .circleci/config.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Paste the copied configuration in the created file. Create a workflow at the bottom to run your job. The workflow should be similar to that below.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;workflows:
  test-workflow:
    jobs:
      - newman-collection-run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Your entire &lt;code&gt;.circleci/config.yml&lt;/code&gt; file should look like that below. Take special care of the indention &lt;code&gt;newman/newman-run&lt;/code&gt; step(lines 13 –15) to avoid indention related errors.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;We are almost done. However, as you might have noticed, we have two instances of &lt;code&gt;$POSTMAN_API_KEY&lt;/code&gt; (lines 14 and 15) in our &lt;code&gt;.circleci/config.yml&lt;/code&gt; file and these must be substituted with an actual value on CircleCI. To create a Postman API Key, head over &lt;a href="https://go.postman.co/settings/me/api-keys" rel="noopener noreferrer"&gt;here&lt;/a&gt; and click &lt;strong&gt;Generate API Key&lt;/strong&gt;. Copy the key and go to the project settings on CircleCI. Add an environment variable called &lt;code&gt;POSTMAN_API_KEY&lt;/code&gt; and paste the Postman API Key in the value box.&lt;/p&gt;

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

&lt;p&gt;While still on CircleCI, go to the &lt;strong&gt;Organization Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Security&lt;/strong&gt; &amp;gt; &lt;strong&gt;Orb Security Settings&lt;/strong&gt;, and select &lt;strong&gt;Yes&lt;/strong&gt; to allow use of third-party orbs. This is because in our CircleCI configuration, we use &lt;code&gt;newman: postman/newman@0.0.2&lt;/code&gt; on line 5 which is not a default CircleCI orb.&lt;br&gt;
Commit and push the changes to GitHub&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git add .circleci/config.yml
$ git commit -m "add circleci config"
$ git push origin 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzayj2syfphje7m3r922.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzayj2syfphje7m3r922.png" alt="Newman report from CircleCI" width="700" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yeeey!&lt;br&gt;
All our tests passed. We can also view our build history on Postman.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvktk3j1ii6zg5b5b6mp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvktk3j1ii6zg5b5b6mp.png" alt="Build history on Postman" width="700" height="366"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As you too have seen, testing your API with Postman is quite basic and you do not need to install any other testing packages into your project like mocha, jest, chai-http for automated testing. All you need is some basic understanding of JavaScript to write test cases on Postman.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this post and wouldn't hesitate to leave me a heart or comment.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Happy coding!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://learning.postman.com/docs/writing-scripts/test-scripts/" rel="noopener noreferrer"&gt;Writing Postman tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/newman" rel="noopener noreferrer"&gt;Newman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learning.postman.com/docs/integrations/ci-integrations/" rel="noopener noreferrer"&gt;Postman CI Integrations&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>testing</category>
      <category>postman</category>
      <category>api</category>
      <category>circleci</category>
    </item>
    <item>
      <title>AutoIdle cuts your Heroku bill by auto-putting your staging and review apps to sleep</title>
      <dc:creator>Mike Mwanje</dc:creator>
      <pubDate>Wed, 27 Oct 2021 21:56:22 +0000</pubDate>
      <link>https://dev.to/mwanjemike/im-here-to-tell-you-about-autoidle-25no</link>
      <guid>https://dev.to/mwanjemike/im-here-to-tell-you-about-autoidle-25no</guid>
      <description>&lt;p&gt;From simple to complex applications, Heroku stands out as a deployment choice for many developers. This is because with Heroku, getting an application up and running is a very simple procedure that abstracts the underlying infrastructure and its scaling needs. However, with more applications running on Heroku is a growing bill even when no traffic is being served. &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fautoidle.com%2F" rel="noopener noreferrer"&gt;AutoIdle&lt;/a&gt; is a Heroku add-on  that helps cut your Heroku bill by automatically putting your staging and review apps to sleep when you don't need them. In this article, we shall see how we can install AutoIdle on a Heroku app and review apps in a pipeline and observe how much we save.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does AutoIdle work?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://medium.com/r/?url=https%3A%2F%2Fautoidle.com%2F" rel="noopener noreferrer"&gt;AutoIdle&lt;/a&gt; typically puts your apps to sleep after 30mins of inactivity and gets them up and running in under 10seconds of a new request. This way, you are not charged for the time the your apps are up but idle. With that stated…&lt;/p&gt;

&lt;h3&gt;
  
  
  …let's get our hands dirty.
&lt;/h3&gt;

&lt;p&gt;Let's create an app called &lt;em&gt;autoidle-saving&lt;/em&gt; in a  Heroku pipeline. Starting from the &lt;em&gt;start&lt;/em&gt; branch of &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fgithub.com%2F123MwanjeMike%2Fautoidle-saving" rel="noopener noreferrer"&gt;this repository&lt;/a&gt;, we shall incrementally build our application to the state in the &lt;em&gt;main&lt;/em&gt; branch; so fork the repository and go &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fdashboard.heroku.com%2Fpipelines%2Fnew" rel="noopener noreferrer"&gt;here&lt;/a&gt; to create a Heroku pipeline. You will be presented with the screen below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7vy0jtfwkp847z9ehh6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7vy0jtfwkp847z9ehh6.png" alt="The newly created Heroku pipeline. It's name is saving0with-autoidle" width="800" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now select the &lt;em&gt;Settings&lt;/em&gt; tab and under the &lt;em&gt;Connect to GitHub&lt;/em&gt; option, search for the forked repository and connect it to the pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4rajd7r5hrcv9hst2wq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4rajd7r5hrcv9hst2wq.png" alt="Connecting the pipeline to a GitHub repository" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After connecting the repository, the &lt;em&gt;Review apps&lt;/em&gt; option should now be available. Click &lt;em&gt;Enable&lt;/em&gt; to have review apps for Pull Requests.&lt;br&gt;
&lt;strong&gt;Note:&lt;/strong&gt; Check the &lt;em&gt;Wait for CI to pass&lt;/em&gt; option if you want HerokuCI to run your tests before deploying to the review app. There is a detailed article on Heroku CI and how to use it &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fdev.to%2Fmwanjemike%2Fbuild-a-ci-cd-pipeline-with-heroku-ci-3de9"&gt;here&lt;/a&gt; that you can check out if you want to learn more about HerokuCI specifically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltvxugf2m1ndf8dkc2jo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltvxugf2m1ndf8dkc2jo.png" alt="Enabling review apps for the pipeline" width="510" height="649"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Return to the pipeline tab now and for each of staging and production sections, click &lt;strong&gt;&lt;em&gt;Add app&lt;/em&gt;&lt;/strong&gt;, and click &lt;strong&gt;&lt;em&gt;Create new app…&lt;/em&gt;&lt;/strong&gt; to create a staging and production app respectively.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwvrjejwvry6yss40janv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwvrjejwvry6yss40janv.png" alt="Creating the staging app for autoidle-saving" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your pipeline now looks like so&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvuiiy4o0yncbz6ktc1t4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvuiiy4o0yncbz6ktc1t4.png" alt="The pipeline after adding a staging and production app" width="800" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let us make one additional tweak on our two apps. We want the staging app to automatically deploy code in the develop branch and the production app that in main branch on every push to the respective branches of the connected GitHub repository.  So, starting with the staging app, click the &lt;em&gt;Configure automatic deploys…&lt;/em&gt; option.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu52755gx2vnm6o32221k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu52755gx2vnm6o32221k.png" alt="autoidle-saving-staging app options" width="520" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will be presented with a screen such as below. Select the develop branch and click &lt;strong&gt;&lt;em&gt;Enable Automatic Deploys&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8y7njhxq7ffxaftxdip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8y7njhxq7ffxaftxdip.png" alt="Enabling automatic deploys on the staging app" width="635" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same goes for the production application, only that for it we shall deploy the main branch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw680tk3ckar4flif4pk9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw680tk3ckar4flif4pk9.png" alt="Enabling automatic deploys on the production app" width="627" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pipeline is now ready and we can make an addition to our app so as to set everything in motion.&lt;br&gt;
Run the commands below to clone the forked repository and install the dependencies of the application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git clone https://github.com/123MwanjeMike/autoidle-saving.git
$ cd autoidle-saving
$ npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now to confirm that all is okay, run &lt;strong&gt;npm start&lt;/strong&gt; in the terminal and you should have the output below&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9jt4iivpyp7daul4j1e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9jt4iivpyp7daul4j1e.png" alt="Terminal output after running npm start" width="424" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F687zxzkquz80rflx8qjj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F687zxzkquz80rflx8qjj.png" alt="Accessing our application through the browser at 127.0.0.1:3000" width="460" height="131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let us deploy to our staging app. So back to the staging section of the pipeline, and  under the app options, click &lt;strong&gt;&lt;em&gt;Deploy a branch…&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;…and deploy the develop branch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjcjk5elh3q0rt2hmjiml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjcjk5elh3q0rt2hmjiml.png" alt="Manually deploying the develop branch to staging app" width="645" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let us try to access our staging app in the browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9yf8qs3mxeaweyydjszj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9yf8qs3mxeaweyydjszj.png" alt="Develop branch successfully deployed" width="506" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are now going to install AutoIdle on the staging app so that it is automatically put to sleep. So if you already &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fdevcenter.heroku.com%2Farticles%2Fheroku-cli%23download-and-install" rel="noopener noreferrer"&gt;downloaded and installed&lt;/a&gt; Heroku CLI on your computer, go to your terminal and login.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Next, we shall add our staging app's git repository to the local repository. To get a Heroku app's git URL, select the app and go to its Settings tab. You should see the &lt;strong&gt;&lt;em&gt;Heroku git URL&lt;/em&gt;&lt;/strong&gt; listed under the app's information.&lt;/p&gt;

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

&lt;p&gt;Copy the link and add it as a remote repository&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git remote add staging-app https://git.heroku.com/autoidle-saving-staging.git
$ git remote -v
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a list of your remote repositories in the terminal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftypugn3iqocwcavg7fva.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftypugn3iqocwcavg7fva.png" alt="Output after running git remote -v" width="604" height="66"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we shall add AutoIdle to our application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku addons:create autoidle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjrlffs3vxmi9skv8vtf2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjrlffs3vxmi9skv8vtf2.png" alt="AutoIdle successfully added to the staging app" width="652" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We shall also add AutoIdle to our review apps. So, create a new branch &lt;em&gt;heroku-config&lt;/em&gt; and add  an &lt;strong&gt;&lt;em&gt;app.json&lt;/em&gt;&lt;/strong&gt; file, a manifest format for describing Heroku  web apps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git checkout -b heroku-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the &lt;em&gt;app.json&lt;/em&gt; has the content below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
   "environments": {
      "review": {
         "addons": ["autoidle:hobby"]
      }
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now commit and push your changes to the GitHub repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git add app.json
$ git commit -m "AutoIdle configs for review apps"
$ git push -u origin heroku-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a PR for the &lt;em&gt;heroku-config&lt;/em&gt; branch against develop and merge it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9i4qvt3tmd21qaon30ji.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9i4qvt3tmd21qaon30ji.png" alt="Our pipeline after merging our PR" width="800" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now lets make a final addition to our app to have a review app and we compare the saving with AutoIdle. The endpoint is for the 'Hello World, happy saving!' message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Welcome!, let's save with AutoIdle 🎊`&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/salutation/:name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Happy saving &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/world&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World, happy saving!&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sorry, resource 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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Listening on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit and push your changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git commit -am "hello-world"
$ git push -u origin hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a PR for hello-world against develop and we should have a new review app for that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy0kp7phqdfolxmfrmn87.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy0kp7phqdfolxmfrmn87.png" alt="Hello-world review app running" width="580" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's observe the saving on the &lt;a href="https://app.autoidle.com/" rel="noopener noreferrer"&gt;AutoIdle dashboard&lt;/a&gt;. In your terminal, run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku addons:open autoidle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will be taken to the browser and presented with the screen below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2eli00fd7xmhyokl9qbu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2eli00fd7xmhyokl9qbu.png" alt="AutoIdle dashboard with the staging and review apps running" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see that all our apps are running and we  don't have any available saving data, so shall check again after some time has passed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1edz1au0lynu6wnhy2jy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1edz1au0lynu6wnhy2jy.png" alt="AutoIdle dashboard with available apps stopped" width="700" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see that both our staging and review applications were automatically stopped without us having to click any button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With cloud computing, every extra second your application is running counts and you want to be as lean as possible. In this tutorial, we only used AutoIdle on two applications, but imagine how much you could save with a large application with multiple contributors and new PRs created by the minute, each with a new review app. The cost of having apps running while not actively in use can be unnecessarily large and overwhelming. I hope &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fautoidle.com%2F" rel="noopener noreferrer"&gt;AutoIdle&lt;/a&gt; is a tool you and your organization can leverage to reduce costs.&lt;/p&gt;

&lt;p&gt;Till next time,&lt;br&gt;
&lt;em&gt;Happy saving!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>autoidle</category>
      <category>heroku</category>
      <category>cicd</category>
      <category>devops</category>
    </item>
    <item>
      <title>Build a CI/CD pipeline with Heroku CI.</title>
      <dc:creator>Mike Mwanje</dc:creator>
      <pubDate>Fri, 01 Oct 2021 20:56:22 +0000</pubDate>
      <link>https://dev.to/mwanjemike/build-a-ci-cd-pipeline-with-heroku-ci-3de9</link>
      <guid>https://dev.to/mwanjemike/build-a-ci-cd-pipeline-with-heroku-ci-3de9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Heroku CI automatically runs your app’s test suite with every push to your app’s GitHub repository, enabling you to easily review test results before merging or deploying changes to your codebase.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Besides strong Dev/prod parity, using Heroku CI comes with many benefits key of which include;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parallel Test Runs.&lt;/li&gt;
&lt;li&gt;Browser tests and User Acceptance Tests&lt;/li&gt;
&lt;li&gt;Seamless integration with Heroku pipelines&lt;/li&gt;
&lt;li&gt;Easy customization of test dependencies in the test environment&lt;/li&gt;
&lt;li&gt;A Simple Integrated Solution (integration, hosting and deployment)&lt;/li&gt;
&lt;li&gt;Simple, prescriptive developer experience in modern CI/CD solutions&lt;/li&gt;
&lt;li&gt;Management support&lt;/li&gt;
&lt;li&gt;Many supported languages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, we shall build a Heroku Flow CI/CD pipeline that utilizes Heroku CI.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fifmljasgdg5lxrg1v6pl.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fifmljasgdg5lxrg1v6pl.gif" alt="Heroku Flow illustration" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Part 1: App init.
&lt;/h3&gt;

&lt;p&gt;We are going to build a simple node.js application that calculates the factorial of a number. We shall however start with its api and then add the factorial functionality later. This is so we can see Heroku CI in action.&lt;br&gt;
To start, fork the repository &lt;a href="https://github.com/123MwanjeMike/cicd-with-herokuci.git" rel="noopener noreferrer"&gt;here&lt;/a&gt; and then clone your forked repository to your computer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git clone https://github.com/123MwanjeMike/cicd-with-herokuci.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, change to the created directory and switch to the start branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd cicd-with-herokuci/
$ git checkout start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize the project with &lt;em&gt;npm init&lt;/em&gt; and then install express&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm init -y
$ npm install express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should have a new file &lt;em&gt;package.json&lt;/em&gt; in your directory. Open it and add the start script &lt;em&gt;node index.js&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
  "test": "echo \"Error: no test specified\" &amp;amp;&amp;amp; exit 1",
  "start": "node index.js"
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a simple api for the app in &lt;em&gt;index.js&lt;/em&gt; file with the code below.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome to the Factorial calculator 🎊&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Resource 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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Listening on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Running &lt;em&gt;npm start&lt;/em&gt; should give a similar output as below&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyvmk1yqkuohr6daz0qay.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyvmk1yqkuohr6daz0qay.png" alt="Output after running npm start" width="503" height="100"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pa3od69xlyj6vmnci5e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pa3od69xlyj6vmnci5e.png" alt="When base url is accessed in browser" width="553" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We now have a basic setup for our application.&lt;/p&gt;
&lt;h3&gt;
  
  
  Part 2: The pipeline.
&lt;/h3&gt;

&lt;p&gt;We shall be adding our application to a Heroku pipeline.&lt;br&gt;
First we need to tell Heroku which services to run and how to run these. To do this, we shall add a &lt;em&gt;Procfile&lt;/em&gt; file at the root of our directory by running;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ echo "web: node index.js" &amp;gt;&amp;gt; Procfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit and push your changes to the remote repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git add .
$ git commit -m "&amp;lt;enter your message here&amp;gt;"
$ git push origin start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now move &lt;a href="https://dashboard.heroku.com/pipelines/new" rel="noopener noreferrer"&gt;here&lt;/a&gt; if you have a Heroku account and create a new Heroku pipeline. Remember to connect your forked repository as you create the pipeline. You should end up with a screen like below.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuc079x8g495omtvrw4ms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuc079x8g495omtvrw4ms.png" width="700" height="236"&gt;&lt;/a&gt;&lt;br&gt;
Click &lt;strong&gt;Enable Review Apps&lt;/strong&gt; to have deploy previews for the pull requests(PRs) before they are merged.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F610v1nntedv5yh35secs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F610v1nntedv5yh35secs.png" width="499" height="646"&gt;&lt;/a&gt;&lt;br&gt;
Next, under the staging section of the pipeline, click &lt;strong&gt;Add app&lt;/strong&gt; and create a new app. Its common practice to have the staging app deployed from the develop branch and the production app from the main branch.&lt;br&gt;
In this case, my factorial-app is being deployed from my default branch(main).&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4g5197zkc3z7isw8pwm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4g5197zkc3z7isw8pwm.png" width="700" height="253"&gt;&lt;/a&gt;&lt;br&gt;
Click settings and scroll to the Heroku CI section. Click Enable Heroku CI.&lt;br&gt;
&lt;strong&gt;Note:&lt;/strong&gt; Heroku gives 1000 free dyno hours per month to its verified users and these can be used to access the Heroku CI service for free until they are depleted — in which case you start paying per extra dyno minutes used.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvhdslvmxyh6l1o2gjm6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvhdslvmxyh6l1o2gjm6.png" alt="Before enabling Heroku CI" width="700" height="238"&gt;&lt;/a&gt;&lt;br&gt;
Our pipeline is now set up and we are ready for action.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ty840p20izdvjlc84uf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ty840p20izdvjlc84uf.png" width="700" height="235"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Part 3: The application’s factorials service
&lt;/h3&gt;

&lt;p&gt;We are now going to add a service to our application. We want to enable it calculate the factorial of a number. This will be through a get request with path parameters. We shall use the test driven development approach while at it, so install the test runner mocha and the assertion library chai.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm install -D mocha chai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the &lt;em&gt;package.json&lt;/em&gt; file and find the test key under scripts and change its value to have &lt;em&gt;mocha *.test.js || true&lt;/em&gt;. Your scripts now look like so.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
  "test": "mocha *.test.js || true",
  "start": "node index.js"
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are basically telling mocha to execute &lt;em&gt;.test.js&lt;/em&gt; files without displaying errors when we run &lt;em&gt;npm test&lt;/em&gt;.&lt;br&gt;
Let us now create our test file &lt;em&gt;factorial.test.js&lt;/em&gt; that will have our tests which will be automated later on.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chai&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;factorial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./factorial&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&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;Factorial test&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="nf"&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;Factorial(0) = 1&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;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&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;Factorial(1) = 1&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;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&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;Factorial(5) = 120&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;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&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;Factorial(171) = Infinity&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;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;171&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;Infinity&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;Next, create the file &lt;em&gt;factorial.js&lt;/em&gt; to which we shall write our function that calculates the factorial of a number.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;factorial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;number&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;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="nx"&gt;i&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;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;We can run our test suite with &lt;em&gt;npm test&lt;/em&gt; and get the output as below&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff95uen8xavwwv6yc1umq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff95uen8xavwwv6yc1umq.png" alt="npm test output" width="604" height="225"&gt;&lt;/a&gt;&lt;br&gt;
Now let us update our &lt;em&gt;index.js&lt;/em&gt; too with an end point that can serve requests for the factorials of numbers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&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;factorial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./factorial&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;headers&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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome to the Factorial calculator 🎊&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/docs`&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/docs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;headers&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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Documentation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/factorial/&amp;lt;number&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The factorial of &amp;lt;number&amp;gt; is &amp;lt;result&amp;gt;`&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/factorial/5`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The factorial of 5 is 120`&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="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/factorial/:number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;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;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;400&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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`'&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' is not a number.`&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;number&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`The factorial of &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is Infinity`&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`The factorial of &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;number&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;headers&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;404&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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Resource not found.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/docs`&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;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Listening on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;I added a docs endpoint to provide documentation for our service request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 4: The magic
&lt;/h3&gt;

&lt;p&gt;The moment of truth has come. Now we shall push our changes again to GitHub, open a pull request then observe how it all works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git add .
$ git commit -m "Factorial service"
$ git push origin start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After opening the pull request, we see that Heroku CI tests passed and that the branch was successfully deployed. We can also go ahead to checkout the deployment.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6uy1epojgaeog3uuya6h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6uy1epojgaeog3uuya6h.png" alt="Open pull request on GitHub" width="700" height="356"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flsasn5rfb5iipivcq34h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flsasn5rfb5iipivcq34h.png" alt="Deploy preview for our pull request" width="641" height="160"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs77apd928zi3vcxy0qn4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs77apd928zi3vcxy0qn4.png" alt="All our tests passed on Heroku CI" width="700" height="298"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdlc5g4mobu1ub0btdazs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdlc5g4mobu1ub0btdazs.png" alt="Our Heroku pipeline" width="700" height="365"&gt;&lt;/a&gt;&lt;br&gt;
Finally, lets enable automatic deploys for our factorial-app still under the staging section of our pipeline.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwx6vckpfja1j2rn2znor.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwx6vckpfja1j2rn2znor.png" width="437" height="363"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftosq0ta6la5cg9gvxn00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftosq0ta6la5cg9gvxn00.png" width="652" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From now on, any code push to our main branch will automatically be deployed to the staging environment of our application.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62k54e15bjkuv1l42tqd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62k54e15bjkuv1l42tqd.png" alt="Our changes on main branch deployed to staging" width="700" height="176"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frlf7ebp2putgo9r4yu7c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frlf7ebp2putgo9r4yu7c.png" alt="Factorial app running" width="513" height="132"&gt;&lt;/a&gt;&lt;br&gt;
Since everything is working fine, we can then move/promote the app to production.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6u0t43anwh8cc78tdy1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6u0t43anwh8cc78tdy1.png" width="700" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As you have seen, setting up a CI/CD pipeline with Heroku CI is quite easy and Heroku Flow is a very intuitive workflow for visualizing your code delivery process. Heroku is ideal for small and medium business applications even though large business application with a microservices architecture can still leverage it. With Heroku CI, you can move fast without breaking things.&lt;/p&gt;

&lt;p&gt;For reference, you can find the entire project code in this &lt;a href="https://github.com/123MwanjeMike/cicd-with-herokuci" rel="noopener noreferrer"&gt;repository&lt;/a&gt; with branches start, part-1, part-2, and part-3 that have the resultant code version of each respective part above and the final application code in the main branch.&lt;/p&gt;

&lt;p&gt;This post was sponsored by &lt;a href="https://autoidle.com/" rel="noopener noreferrer"&gt;AutoIdle&lt;/a&gt;. AutoIdle is an add-on that cuts your Heroku bill by automatically putting your staging and review apps to sleep when you don't need them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Happy hacking.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
      <category>github</category>
      <category>herokuci</category>
    </item>
    <item>
      <title>Project management with Github Issues.</title>
      <dc:creator>Mike Mwanje</dc:creator>
      <pubDate>Mon, 06 Sep 2021 15:23:41 +0000</pubDate>
      <link>https://dev.to/mwanjemike/project-management-with-github-issues-3foc</link>
      <guid>https://dev.to/mwanjemike/project-management-with-github-issues-3foc</guid>
      <description>&lt;p&gt;Software projects might be different from other types of projects in terms of visibility, complexity, conformity, and flexibility as Fred Brooks points out; but one thing in common with all projects is change. Essentially, project management is change management and this is what project management tools help us with. Tools like Trello, ClickUp, Jira are built to enable us best plan and track our work -  herein, you'll learn to do the same with Github Issues.&lt;/p&gt;

&lt;h4&gt;
  
  
  But first, why Github Issues?
&lt;/h4&gt;

&lt;p&gt;Given that Github is a leading software development platform, most project management tools support integration with it as a way to bridge management and development teams. However, the underlying problem still remains: this is not where the code is and developers have to leave the platform to update their work item status on these project management tools. In addition, automation on these is not as easy and the pricing of these tools is maybe high.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are Github Issues.
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/issues" rel="noopener noreferrer"&gt;Github Issues&lt;/a&gt; is a collection of Github products that include labels and milestones, issues, project boards, and projects(beta). All these products can be used together to easily plan and track your progress on your software project. The scope of this article will be limited to issues and Github project boards and that's what will be covered in the tutorial section.&lt;/p&gt;

&lt;h4&gt;
  
  
  Github project boards
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;Project boards on GitHub help you organize and prioritize your work. You can create project boards for specific feature work, comprehensive roadmaps, or even release checklists. With project boards, you have the flexibility to create customized workflows that suit your needs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Depending on your needs, you can either  create repository, user-owned, or organization-wide project boards all with different scope. A card on a project board represents either an  issue, a pull request or a note. These cards contain metadata about the issue or pull request the like labels, assignee(s), the status, and who opened it. Github project boards also provides you with templates to create a new board some of which come with built in automation. They include basic kanban, automated kanban, automated kanban with review, and  bug triage. Now, let's see how this works.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tutorial
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Create the project board.&lt;br&gt;
First, &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fdocs.github.com%2Fen%2Fissues%2Forganizing-your-work-with-project-boards%2Fmanaging-project-boards%2Fcreating-a-project-board%23creating-a-repository-project-board" rel="noopener noreferrer"&gt;create a repository project board&lt;/a&gt; from the Automated Kanban template under the project template drop-down. I'll call my board User App.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fioaa9w7zyii5e30no5nd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fioaa9w7zyii5e30no5nd.png" alt="my created project board" width="700" height="336"&gt;&lt;/a&gt;&lt;br&gt;
(Optional) Rename the To do Column to &lt;em&gt;Product Backlog&lt;/em&gt; and add a column called &lt;em&gt;Sprint Backlog&lt;/em&gt; with the To do preset, and click the &lt;strong&gt;Reopened&lt;/strong&gt; checkbox under &lt;strong&gt;Move issues here when…&lt;/strong&gt; and move it after the Product Backlog column.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdg1eyt7oqr9xgohmdeer.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdg1eyt7oqr9xgohmdeer.png" alt="my new project board after renaming and creating the new column" width="700" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Add notes to the project board&lt;br&gt;
Next, we will &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fdocs.github.com%2Fen%2Fissues%2Forganizing-your-work-with-project-boards%2Ftracking-work-with-project-boards%2Fadding-notes-to-a-project-board%23adding-notes-to-a-project-board" rel="noopener noreferrer"&gt;add notes&lt;/a&gt; to our project board under the Product Backlog list and after &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fdocs.github.com%2Fen%2Fissues%2Forganizing-your-work-with-project-boards%2Ftracking-work-with-project-boards%2Fadding-notes-to-a-project-board%23converting-a-note-to-an-issue" rel="noopener noreferrer"&gt;convert them to issues&lt;/a&gt;. These are features  we plan to work on.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcaf0dgelysjib0nhqi5d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcaf0dgelysjib0nhqi5d.png" alt="the newly created notes converted to issues" width="515" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Assign issues.&lt;br&gt;
Now that we have a good understanding of what we plan to do, we shall start embarking on the issues. So say, for the first sprint we are working on &lt;em&gt;Signup with email&lt;/em&gt; and &lt;em&gt;Login with email and password&lt;/em&gt;. We shall move these cards to the Sprint Backlog column and &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fdocs.github.com%2Fen%2Fissues%2Ftracking-your-work-with-issues%2Fassigning-issues-and-pull-requests-to-other-github-users%23assigning-an-individual-issue-or-pull-request" rel="noopener noreferrer"&gt;assign each&lt;/a&gt; issue to an individual.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Update &lt;em&gt;In Progress&lt;/em&gt; column automation.&lt;br&gt;
We will now &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fdocs.github.com%2Fen%2Fissues%2Forganizing-your-work-with-project-boards%2Fmanaging-project-boards%2Fconfiguring-automation-for-project-boards" rel="noopener noreferrer"&gt;update automation&lt;/a&gt; for the  &lt;em&gt;In Progress&lt;/em&gt; column move pull requests here when newly added, reopened, and pending approval by reviewer. This will enable us know what tasks are currently being worked upon.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3qwm8ulska9foa3bxjwu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3qwm8ulska9foa3bxjwu.png" alt="Update In Progress column automation illustration" width="558" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Moment of truth&lt;/strong&gt;&lt;br&gt;
With the above setup complete, we are now good to go. When an assignee opens a PR(Pull Request) and &lt;a href="https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#manually-linking-a-pull-request-to-an-issue" rel="noopener noreferrer"&gt;links it to the issue&lt;/a&gt;, we shall see a new card under the &lt;em&gt;In Progress&lt;/em&gt; column that corresponds to the PR, and when the PR is merged, both the issue card and PR card will automatically be moved to the &lt;em&gt;Done&lt;/em&gt; column.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/djJI9dJtqZw"&gt;
&lt;/iframe&gt;
&lt;br&gt;
Github project board: &lt;a href="https://github.com/123MwanjeMike/user-app/projects/1" rel="noopener noreferrer"&gt;https://github.com/123MwanjeMike/user-app/projects/1&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Final thoughts.
&lt;/h3&gt;

&lt;p&gt;This is only the tip of the iceberg that can be done with Github Issues and with good mastery of the Github project management products, you are spoilt for choice.&lt;br&gt;
One only challenge I’ve found with Github Issues is that clients and management may have little or totally no knowledge of how to use these products. In such cases, you want to rethink the solution to get a workaround.&lt;/p&gt;

</description>
      <category>github</category>
      <category>projects</category>
      <category>projectmanagement</category>
      <category>issues</category>
    </item>
    <item>
      <title>How different is CommonJs require from ES6 import?</title>
      <dc:creator>Mike Mwanje</dc:creator>
      <pubDate>Sat, 28 Aug 2021 09:28:36 +0000</pubDate>
      <link>https://dev.to/mwanjemike/how-different-is-commonjs-require-from-es6-import-hh1</link>
      <guid>https://dev.to/mwanjemike/how-different-is-commonjs-require-from-es6-import-hh1</guid>
      <description>&lt;p&gt;In JavaScript, you can use either ECMAScript 6(ES6) modules or CommonJs modules in your project and there are a few differences between these that do affect how your program modules are loaded. In this article, I explore how each works and how it may affect your program execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  CommonJs modules.
&lt;/h3&gt;

&lt;p&gt;CommonJs is the original and default module system of Node.js which uses require and module.exports. Below is an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Importing modules&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&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;fileDelete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./fileDeleter&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;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./fileNamer&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;writeFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Exporting writeFile module&lt;/span&gt;
&lt;span class="nx"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With require, you can’t selectively load only the modules you need. This means even the fileDelete module from the example above will be imported even if it is not needed or used anywhere. Additionally, importing of the modules is synchronous which means that fileName module can’t be imported before fs and fileDelete modules are imported, and a failure to import fileDelete will cause run-time errors even if it is not used anywhere in our program. CommonJS modules are the choice for the node.js server.&lt;/p&gt;

&lt;h3&gt;
  
  
  ECMAScript modules
&lt;/h3&gt;

&lt;p&gt;ECMAScript modules are relatively newer and use import and export. Below is the transformation of our CommonJs example from above to ESM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Importing modules&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fileDelete&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;./fileDeleter&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;fileName&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;./fileNamer&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;writeFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Exporting writeFile module&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With import, you load only the modules you need. For example, the fileDelete module from the above will not be imported since it is not used anywhere. Additionally, the importing of the modules is asynchronous which means that both fs and fileName are imported at the same time. You generally want to use ESM for your new projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;…how about .cjs and .mjs?&lt;/strong&gt;&lt;br&gt;
.cjs is a file extension for CommonJS modules while .mjs is a file extension for ECMAScript module. Node.js by default treats .js files as CommonJS modules. You can change this by adding "type": "module"to your package.json file so you can use ECMAScript modules (in your .mjs files) within a Node.js environment. This is what Google Chrome &lt;a href="https://v8.dev/" rel="noopener noreferrer"&gt;V8&lt;/a&gt; recommends.&lt;/p&gt;

&lt;p&gt;I hope this was helpful to you and for further reading, do checkout &lt;a href="https://v8.dev/features/modules" rel="noopener noreferrer"&gt;JavaScript modules&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Happy coding!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>es6modules</category>
      <category>commonjs</category>
    </item>
  </channel>
</rss>
