<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chris Muir</title>
    <description>The latest articles on DEV Community by Chris Muir (@chriscmuir).</description>
    <link>https://dev.to/chriscmuir</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%2F570567%2F504d6549-fb5b-461d-83b2-33ee62b8648c.png</url>
      <title>DEV Community: Chris Muir</title>
      <link>https://dev.to/chriscmuir</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chriscmuir"/>
    <language>en</language>
    <item>
      <title>Kubernetizing a basic Slack app on Oracle cloud (OCI)</title>
      <dc:creator>Chris Muir</dc:creator>
      <pubDate>Mon, 21 Jun 2021 03:00:53 +0000</pubDate>
      <link>https://dev.to/chriscmuir/kubernetizing-a-basic-slack-app-on-oracle-cloud-oci-3dkk</link>
      <guid>https://dev.to/chriscmuir/kubernetizing-a-basic-slack-app-on-oracle-cloud-oci-3dkk</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W-D-2GJE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tby87sfufmi7vri88a7u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W-D-2GJE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tby87sfufmi7vri88a7u.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my previous two blog posts I spoke about how to &lt;a href="https://dev.to/chriscmuir/just-my-notes-creating-a-basic-slack-app-pf1"&gt;create a basic Slack app&lt;/a&gt; using NodeJS and Slack's Bolt JavaScript framework, and then &lt;a href="https://dev.to/chriscmuir/dockerizing-a-basic-slack-app-pdc"&gt;Dockerizing&lt;/a&gt; it.&lt;/p&gt;

&lt;p&gt;In this blog post I want to capture my notes about deploying the application to a Kubernetes cluster using Oracle's cloud offerings a.k.a. Oracle Cloud Infrastructure (OCI).  As per the previous blog posts, there's nothing staggeringly new in this blog post, I'm simply capturing my notes on how to do this so I don't have to remember all the details for next time.&lt;/p&gt;

&lt;p&gt;In this post we're going to use OCI to setup a Kubernetes cluster, configure an OCI user account to be remotely managed from my Mac, and then use Docker and kubectl on my Mac to deploy the application to the Kubernetes cluster on OCI.&lt;/p&gt;

&lt;h1&gt;
  
  
  Assumptions
&lt;/h1&gt;

&lt;p&gt;For this blog post I'm making a few assumptions:&lt;/p&gt;

&lt;p&gt;(a) We have access to an OCI tenancy&lt;br&gt;
(b) We have privileges in OCI to create a Kubernetes Cluster and deploy to the Container Registry&lt;/p&gt;

&lt;p&gt;For (b) if our user account is an OCI administrator we have all the privileges required, otherwise we need to follow these OCI documents to grant the appropriate privileges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en-us/iaas/Content/ContEng/Concepts/contengpolicyconfig.htm"&gt;OCI Kubernetes Container Engine - Policy Configuration for Cluster Creation and Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en-us/iaas/Content/Registry/Concepts/registrypolicyrepoaccess.htm"&gt;OCI Container Registry - Policies to Control Repository Access&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Setting up a Kubernetes cluster on OCI
&lt;/h1&gt;

&lt;p&gt;In OCI there is a number of ways to setup cloud resources like a Kubernetes cluster, such as using the OCI web console, Terraform, a command line client (a.k.a. OCI CLI) and more.  We'll take a shortcut (read: cheat) and use the Kubernetes wizard in the OCI console.&lt;/p&gt;

&lt;p&gt;First we'll create a compartment to segment the Kubernetes work from other work in our OCI tenancy.&lt;/p&gt;

&lt;p&gt;(1) With an OCI tenancy created and a login to the OCI console, via the hamburger menu select  &lt;em&gt;&lt;em&gt;Identity &amp;amp; Security&lt;/em&gt;&lt;/em&gt; then &lt;em&gt;&lt;em&gt;Compartments&lt;/em&gt;&lt;/em&gt; from the sub menu under the &lt;em&gt;&lt;em&gt;Identity&lt;/em&gt;&lt;/em&gt; heading.&lt;/p&gt;

&lt;p&gt;(2) Create a compartment "k8s-demo-compartment" under the root compartment&lt;/p&gt;

&lt;p&gt;(3) Via the hamburger menu select &lt;em&gt;&lt;em&gt;Developer Services&lt;/em&gt;&lt;/em&gt; then &lt;em&gt;&lt;em&gt;Kubernetes Clusters (OKE)&lt;/em&gt;&lt;/em&gt; under the &lt;em&gt;&lt;em&gt;Containers &amp;amp; Artifacts&lt;/em&gt;&lt;/em&gt; heading.&lt;/p&gt;

&lt;p&gt;(4) On the left hand side of the screen, under &lt;em&gt;&lt;em&gt;Compartment&lt;/em&gt;&lt;/em&gt; select the compartment "k8s-demo-compartment" that you created earlier.&lt;/p&gt;

&lt;p&gt;(5) Select the Create Cluster button.&lt;/p&gt;

&lt;p&gt;(6) In the result &lt;em&gt;&lt;em&gt;Create Cluster&lt;/em&gt;&lt;/em&gt; dialog select the &lt;em&gt;&lt;em&gt;Quick Create&lt;/em&gt;&lt;/em&gt; option.  This option is designed to create all the necessary Kubernetes artifacts including a virtual cloud network (VCN), various gateways, the Kubernetes cluster working nodes and so on.  Select &lt;em&gt;&lt;em&gt;Launch Workflow&lt;/em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;(7) In the resulting &lt;em&gt;&lt;em&gt;Quick Create Cluster&lt;/em&gt;&lt;/em&gt; set the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change the cluster &lt;em&gt;&lt;em&gt;Name&lt;/em&gt;&lt;/em&gt; to "k8s-demo-cluster"&lt;/li&gt;
&lt;li&gt;Ensure the &lt;em&gt;&lt;em&gt;Kubernetes API Endpoint&lt;/em&gt;&lt;/em&gt; is set to &lt;em&gt;&lt;em&gt;Public Endpoint&lt;/em&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Ensure the &lt;em&gt;&lt;em&gt;Kubernetes Worker Nodes&lt;/em&gt;&lt;/em&gt; is set to &lt;em&gt;&lt;em&gt;Private Workers&lt;/em&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Select the &lt;em&gt;&lt;em&gt;VM.Standard.E3.Flex&lt;/em&gt;&lt;/em&gt; &lt;em&gt;&lt;em&gt;Shape&lt;/em&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Limit the VM to one OCPU and 16GB memory&lt;/li&gt;
&lt;li&gt;Leave the &lt;em&gt;&lt;em&gt;Number of nodes&lt;/em&gt;&lt;/em&gt; at 3.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(8) Click &lt;em&gt;&lt;em&gt;Next&lt;/em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The resulting screen will show us the progress in creating the various artifacts needed for the Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;(9) Once all work is completed click &lt;em&gt;&lt;em&gt;Close&lt;/em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The OCI console will then place us in the &lt;em&gt;&lt;em&gt;Cluster Details&lt;/em&gt;&lt;/em&gt; screen.  Note the status of the cluster will be &lt;em&gt;&lt;em&gt;Creating&lt;/em&gt;&lt;/em&gt;.  Wait until it reads &lt;em&gt;&lt;em&gt;Active&lt;/em&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;(10) Once done select the &lt;em&gt;&lt;em&gt;Node Pools&lt;/em&gt;&lt;/em&gt; option in the left hand-side menu, and in the resulting page select &lt;em&gt;&lt;em&gt;pool1&lt;/em&gt;&lt;/em&gt; and then &lt;em&gt;&lt;em&gt;Nodes&lt;/em&gt;&lt;/em&gt; in the left hand-side menu.  &lt;/p&gt;

&lt;p&gt;The resulting screen will show us the progress in creating the nodes needed for the Kubernetes cluster.  Once set to 'Ready' the Kubernetes cluster is fully setup ready for the next steps.&lt;/p&gt;
&lt;h1&gt;
  
  
  Where to from here?
&lt;/h1&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/chriscmuir/dockerizing-a-basic-slack-app-pdc"&gt;previous blog&lt;/a&gt; we deployed the Slack application to our local Docker instance.  Our goal in this blog hereafter is to deploy the application to OCI to run inside the Kubernetes cluster we just created.  But there's a few steps we must do before we can get to this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First we need to configure an OCI user and an authentication token to allow connections from the various tools we're going to use on our local machine, a Mac in my case, to deploy our application to OCI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once we've setup an OCI user with the appropriate settings, we'll then use Docker to deploy our local image to an OCI Container Registry (OCIR).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally we'll use a local installation of the Kubernetes kubectl command line tool to hook up the Kubernetes cluster with the OCIR deployed image.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's run through those steps now:&lt;/p&gt;
&lt;h1&gt;
  
  
  Configure our OCI user with an auth token to deploy to OCI
&lt;/h1&gt;

&lt;p&gt;As mentioned previously, our next piece of work is to setup our OCI user with an authentication token.  This will allow us to connect to OCI from various tools we're going to use on our local machine to deploy our application to OCI.&lt;/p&gt;

&lt;p&gt;(11) In the OCI Console in the top right of the screen, select the user &lt;em&gt;&lt;em&gt;Profile&lt;/em&gt;&lt;/em&gt; button then select the &lt;em&gt;&lt;em&gt;User Settings&lt;/em&gt;&lt;/em&gt; option.&lt;br&gt;
(12) In the left hand-side menu select &lt;em&gt;&lt;em&gt;Auth Tokens&lt;/em&gt;&lt;/em&gt;.&lt;br&gt;
(13) Select the &lt;em&gt;&lt;em&gt;Generate Token&lt;/em&gt;&lt;/em&gt; button, and in the resulting dialog give the Description "Token for Kubernetes deployment".  Select the &lt;em&gt;&lt;em&gt;Generate Token&lt;/em&gt;&lt;/em&gt; button.&lt;br&gt;
(14) In the resulting screen select the &lt;em&gt;&lt;em&gt;Show&lt;/em&gt;&lt;/em&gt; button, and record the hidden token.  Think of this as a password we're going to use momentarily to access OCI from your local machine.&lt;/p&gt;
&lt;h1&gt;
  
  
  Create an OCI Container Registry
&lt;/h1&gt;

&lt;p&gt;Next we'll setup an OCI Container Registry ready in a moment to receive/push our image from our local machine:&lt;/p&gt;

&lt;p&gt;(15) In the OCI Console select the hamburger menu, then &lt;em&gt;&lt;em&gt;Developer Services&lt;/em&gt;&lt;/em&gt; followed by &lt;em&gt;&lt;em&gt;Container Registry&lt;/em&gt;&lt;/em&gt; under the &lt;em&gt;&lt;em&gt;Containers &amp;amp; Artifact&lt;/em&gt;&lt;/em&gt; heading.&lt;br&gt;
(16) In the resulting screen on the left hand side, select the "k8s-demo-compartment"  &lt;em&gt;&lt;em&gt;Compartment&lt;/em&gt;&lt;/em&gt;.&lt;br&gt;
(17) Select the &lt;em&gt;&lt;em&gt;Create Repository&lt;/em&gt;&lt;/em&gt; button.&lt;br&gt;
(18) In the resulting &lt;em&gt;&lt;em&gt;Create Repository&lt;/em&gt;&lt;/em&gt; dialog enter a &lt;em&gt;&lt;em&gt;Repository Name&lt;/em&gt;&lt;/em&gt; of "scratchslackapp" (all in lowercase) and set the &lt;em&gt;&lt;em&gt;Access&lt;/em&gt;&lt;/em&gt; to &lt;em&gt;&lt;em&gt;Public&lt;/em&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;h1&gt;
  
  
  Push our local application Docker image to the OCI Container Registry
&lt;/h1&gt;

&lt;p&gt;In order to push our local application Docker image to the OCI Container Registry we need to login to OCI via Docker on our local machine.  To do this we will issue the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker login &amp;lt;tenancy-region-key&amp;gt;.ocir.io --username &amp;lt;tenancy-id&amp;gt;/&amp;lt;user-name&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;(19) To determine the tenancy-region-code, in the OCI Console click on the user &lt;em&gt;&lt;em&gt;Profile&lt;/em&gt;&lt;/em&gt; button again in the top right hand of the screen then select the &lt;em&gt;&lt;em&gt;Tenancy&lt;/em&gt;&lt;/em&gt; option.  In the resulting screen we will have the value &lt;em&gt;&lt;em&gt;Home Region&lt;/em&gt;&lt;/em&gt; which is the region your tenancy sits.  For example "US East (Ashburn)".&lt;br&gt;&lt;br&gt;
(20) Once we known the region, in the table in the following documentation &lt;a href="https://docs.oracle.com/en-us/iaas/Content/General/Concepts/regions.htm"&gt;page&lt;/a&gt; lookup the corresponding region key needed for the login statement above in lowercase.  For example for "US East (Ashburn)" the region key is "IAD".  Convert this to lowercase.&lt;br&gt;
(21) Still on the same screen, also note the &lt;em&gt;&lt;em&gt;Object Storage Namespace&lt;/em&gt;&lt;/em&gt; value.  This is the tenancy ID value.  For customer purchased tenancies this will be something we've agreed with Oracle beforehand (e.g. 'acme' or some other meaningful tenancy identifier for our org).  If we're using the OCI free tier this will be an Oracle generated identifier, such as "kdhkv4bxlaap".&lt;br&gt;
(22) Also record the tenancy &lt;em&gt;&lt;em&gt;Name&lt;/em&gt;&lt;/em&gt;.  This is the tenancy name and will be required in (much) later steps in this blog.&lt;br&gt;
(23) Finally the user name is simply the user name we use to login into OCI, such as "&lt;a href="mailto:joe.doe@oracle.com"&gt;joe.doe@oracle.com&lt;/a&gt;".&lt;/p&gt;

&lt;p&gt;In returning to the docker login command using the examples above, it would look like:&lt;/p&gt;

&lt;p&gt;(24) &lt;code&gt;docker login iad.ocir.io --username kdhkv4bxlaap/joe.doe@acme.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;(25) At this point we'll be prompted for a password.  Provide the authentication token we were provided earlier.&lt;/p&gt;

&lt;p&gt;Next within our local Docker repository we want to tag the image we wish to deploy to the OCIR container registry with the name of OCIR container repository name.  We do this by issuing:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker tag &amp;lt;local-image-name&amp;gt;:latest &amp;lt;tenancy-region-key&amp;gt;.ocir.io/&amp;lt;tenancy-id&amp;gt;/&amp;lt;oci-repository-name&amp;gt;:latest&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Using the examples used in the blog so far, this would be:&lt;/p&gt;

&lt;p&gt;(26) &lt;code&gt;docker tag scratchslackapp:latest iad.ocir.io/kdhkv4bxlaap/scratchslackapp:latest&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;From here we push the image to OCI:&lt;/p&gt;

&lt;p&gt;(27) &lt;code&gt;docker push iad.ocir.io/kdhkv4bxlaap/scratchslackapp:latest&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;(28) To see the results, return to the OCI Console, via the hamburger menu select &lt;em&gt;&lt;em&gt;Developer Services&lt;/em&gt;&lt;/em&gt; followed by &lt;em&gt;&lt;em&gt;Container Registry&lt;/em&gt;&lt;/em&gt; under the &lt;em&gt;&lt;em&gt;Containers &amp;amp; Artifact&lt;/em&gt;&lt;/em&gt; heading.&lt;br&gt;
(29) Ensure the "k8s-demo-compartment" is still selected on the left-hand side of the screen, then in the middle select and expand the "scratchslackapp" repository you created earlier.  We should now find a single image which represents what we just uploaded.&lt;/p&gt;
&lt;h1&gt;
  
  
  Setup kubectl ready to configure Kubernetes with the image
&lt;/h1&gt;

&lt;p&gt;At this point our image is now in OCI, but hasn't been associated with the Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;In order to work on Kubernetes, Kubernetes provides a command line tool called &lt;em&gt;&lt;em&gt;kubectl&lt;/em&gt;&lt;/em&gt; (a.k.a. kubernetes control).  This can be downloaded from the &lt;a href="https://kubernetes.io/docs/tasks/tools/"&gt;Kubernetes tools website&lt;/a&gt;.  Once installed it must then be configured to talk to our OCI Kubernetes cluster.  The configuration typically goes in the local machine user's home directory in a .kube/config file.&lt;/p&gt;

&lt;p&gt;OCI makes it easy to configure this by providing a handy dialog under the Kubernetes cluster to show us how exactly to set this up.&lt;/p&gt;

&lt;p&gt;(30) Via the OCI console hamburger menu again open the &lt;em&gt;&lt;em&gt;Kubernetes Clusters (OKE)&lt;/em&gt;&lt;/em&gt; option again, then select our cluster.&lt;br&gt;
(31) Press the &lt;em&gt;&lt;em&gt;Access Your Cluster&lt;/em&gt;&lt;/em&gt; button.&lt;br&gt;
(32) In the resulting dialog it will explain how to setup the .kube/config file by downloading and installing &amp;amp; configure the OCI CLI tool, and then running a number of steps.  Follow those steps!&lt;/p&gt;

&lt;p&gt;(33) We can then verify kubectl is working properly by listing the running nodes in your OCI cluster: &lt;code&gt;kubectl get nodes&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Use kubectl to configure Kubernetes with the image
&lt;/h1&gt;

&lt;p&gt;Okay, we're getting close!&lt;/p&gt;

&lt;p&gt;In order to associate the Kubernetes cluster with our image, we first need to setup a Kubernetes 'secret' using the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl create secret docker-registry &amp;lt;secret-name-to-create&amp;gt; --docker-server=&amp;lt;tenancy-region-key&amp;gt;.ocir.io  --docker-username='&amp;lt;tenancy-namespace&amp;gt;/&amp;lt;oci-username&amp;gt;' --docker-password='&amp;lt;auth-token&amp;gt;' --docker-email='&amp;lt;email-address&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Most of the above values are now self explanatory, but the  requires some more explanation.  If our tenancy's users are federated with Oracle Identify Cloud Service such as the OCI free tier uses, we use the format oracleidentitycloudservice/, for example:&lt;/p&gt;

&lt;p&gt;(34 v1) &lt;code&gt;kubectl create secret docker-registry my-kube-secret --docker-server=iad.ocir.io  --docker-username='my-tenancy-name/oracleidentitycloudservice/joe.doe@acme.com' --docker-password='our auth token' --docker-email='joe.doe@acme.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If alternatively our tenancy is a customer owned tenancy without Oracle Identity Cloud Service, the command takes the form:&lt;/p&gt;

&lt;p&gt;(34 v2) &lt;code&gt;kubectl create secret docker-registry my-kube-secret --docker-server=iad.ocir.io  --docker-username='my-tenancy-name/joe.doe@acme.com' --docker-password='our auth token' --docker-email='joe.doe@acme.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;(35) The secret can be verified by issuing: &lt;code&gt;kubectl get secret my-kube-secret --output.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With the secret in place we're then ready to instruct Kubernetes to use the image. This can be done via the kubectl file, but ends up being repetitive and prone to manual errors.&lt;/p&gt;

&lt;p&gt;An alternative approach to create a Kubernetes YAML file that can be used multiple times.&lt;/p&gt;

&lt;p&gt;(36) In your favourite text editor, return to our application's source code and create a new file &lt;em&gt;&lt;em&gt;kube.yaml&lt;/em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;(37) Add the following source code:&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;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;scratchslackapp&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scratchslackapp&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scratchslackapp&lt;/span&gt;
        &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&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;scratchslackapp&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;iad.ocir.io/kdhkv4bxlaap/scratchslackapp:latest&lt;/span&gt;
        &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Always&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
          &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;imagePullSecrets&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;my-kube-secret&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;scratchslackapp-lb&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scratchslackapp&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scratchslackapp&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&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;http&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file contains two main parts, that above the 3 dashes (---) and that below the 3 dashes.&lt;/p&gt;

&lt;p&gt;Above - defines the application to deploy to the Kubernetes pod.  Note how the image maps to our image we just deployed to the OCI Container Registry, the imagePullSecrets uses my-kube-secret we just created, and the containerPort maps to the Docker EXPOSE port of 5000 we configured in the previous blog.&lt;/p&gt;

&lt;p&gt;Below - in order for the application to be exposed to the internet, this creates an OCI load balancer, mapping port 8080 externally to the targetPort 5000 internally for our application.&lt;/p&gt;

&lt;p&gt;(38) With this file saved, return to our command line, cd to the same directory as the yaml file and execute:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl create -f kube.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This command fires Kubernetes to deploy our application to the Kubernetes pod, and spins up a load balancer for the cluster so external calls from Slack can be routed to our running Slack code.  The command will return when the pod is ready.  However the load balancer may take longer to start so be patient! &lt;/p&gt;

&lt;p&gt;(39) To check if the load balancer is ready issue the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl get services&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We will see an output similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                 TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)          AGE
kubernetes           ClusterIP      10.11.0.1       &amp;lt;none&amp;gt;           443/TCP          27h
scratchslackapp-lb   LoadBalancer   10.11.157.133   132.221.40.151   8080:31467/TCP   5m16s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the external-ip for the load balancer says '' we need to wait longer.&lt;/p&gt;

&lt;p&gt;While we're waiting other commands we can issue:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubetcl get all&lt;/code&gt;&lt;br&gt;
&lt;code&gt;kubectl get services&lt;/code&gt;&lt;br&gt;
&lt;code&gt;kubectl describe services scratchslackapp&lt;/code&gt;&lt;br&gt;
&lt;code&gt;kubectl get pods&lt;/code&gt;&lt;br&gt;
&lt;code&gt;kubectl get pod &amp;lt;pod-id-from-previous-command&amp;gt;&lt;/code&gt;&lt;br&gt;
&lt;code&gt;kubectl get deployments scratchslackapp&lt;/code&gt;&lt;br&gt;
&lt;code&gt;kubectl describe deployments scratchslackapp&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once the load balancer is running, the very last thing we need to do is return to Slack, visiting the &lt;a href="https://api.slack.com/"&gt;api.slack.com&lt;/a&gt;, open our application, select the &lt;em&gt;&lt;em&gt;Event Subscriptions&lt;/em&gt;&lt;/em&gt; page and update the &lt;em&gt;&lt;em&gt;Request URL&lt;/em&gt;&lt;/em&gt; to the http (not https as we haven't configured it in this example) address of the external IP address from above, and port 8080 we configured in the kube.yaml file.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://132.221.40.151:8080/slack/events"&gt;http://132.221.40.151:8080/slack/events&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(40 optional) There after if we're happy with our testing from Slack, you can delete the pod deployment by executing:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl delete -f kube.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Done, in 40 steps! (phew)&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Dockerizing a basic Slack app</title>
      <dc:creator>Chris Muir</dc:creator>
      <pubDate>Wed, 16 Jun 2021 05:42:25 +0000</pubDate>
      <link>https://dev.to/chriscmuir/dockerizing-a-basic-slack-app-pdc</link>
      <guid>https://dev.to/chriscmuir/dockerizing-a-basic-slack-app-pdc</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqs7wkflfcvipb18v4oc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqs7wkflfcvipb18v4oc.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a &lt;a href="https://dev.to/chriscmuir/just-my-notes-creating-a-basic-slack-app-pf1"&gt;previous blog&lt;/a&gt; I wrote about creating a basic Slack app using NodeJS, Slack's Bolt JavaScript framework, and ngrok.  In this blog I will extend this to include deploying the application to Docker.&lt;/p&gt;

&lt;p&gt;Like the previous blog, this blog is just capturing my notes on how to do this so I don't have to remember all the steps.&lt;/p&gt;

&lt;p&gt;The main inspiration for the Dockerfile in this post comes from Kathleen Juell's post &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-build-a-node-js-application-with-docker" rel="noopener noreferrer"&gt;How to Build a Node.js Application with Docker&lt;/a&gt;.  All credit goes to Kathleen for summarizing these steps so neatly.&lt;/p&gt;

&lt;h1&gt;
  
  
  Adding the Dockerfile
&lt;/h1&gt;

&lt;p&gt;On the assumption we already have &lt;a href="https://www.docker.com/products/docker-desktop" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; installed on our local machine, within the application we create a Docker configuration file literally called the &lt;em&gt;&lt;em&gt;Dockerfile&lt;/em&gt;&lt;/em&gt;.  &lt;/p&gt;

&lt;p&gt;(1) In the previous application's root directory, in our favourite text editor, create a new file &lt;em&gt;&lt;em&gt;Dockerfile&lt;/em&gt;&lt;/em&gt;.&lt;br&gt;
(2) Copy in the following code:&lt;/p&gt;

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

FROM node:14.7.0
USER node
RUN mkdir -p /home/node/app/node_modules &amp;amp;&amp;amp; chown -R node:node /home/node/app
WORKDIR /home/node/ScratchSlackApp
COPY package*.json ./
RUN npm install
COPY --chown=node:node . .
EXPOSE 5000
CMD [ "node", "app.js" ]


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

&lt;/div&gt;

&lt;p&gt;What this does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FROM node:14.7.0 - defines the version of Node to run in the Docker container.  The tag 14.7.0 represents the current NodeJS LTS version at the time of writing.  Other supported version can be found via the &lt;a href="https://hub.docker.com/_/node/" rel="noopener noreferrer"&gt;Docker Official Node Images&lt;/a&gt; page.&lt;/li&gt;
&lt;li&gt;USER node - allows us to create a new user besides root to run the application&lt;/li&gt;
&lt;li&gt;RUN mkdir &amp;amp;&amp;amp; chown - creates the application directory structure and gives the node user the appropriate ownership on the files&lt;/li&gt;
&lt;li&gt;WORKDIR - sets the default directory for the container to start executing the code from. This is linked to the CMD call explained below&lt;/li&gt;
&lt;li&gt;COPY package*.json ./ - copies the package.json file into the container&lt;/li&gt;
&lt;li&gt;RUN npm install - within in the container downloads the necessary dependencies/libraries defined in the package.json file&lt;/li&gt;
&lt;li&gt;COPY --chown-node:node . . - the two full stops say to copy the code from the base directory of the source, to the base directory of the container.  The chown statement makes the owner the node user we defined earlier in USER.&lt;/li&gt;
&lt;li&gt;EXPOSE 5000 - defines which port the application running in the container will be listening on&lt;/li&gt;
&lt;li&gt;CMD - defines how the application will be started in the container&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of special note from the Node Bolt application example in my &lt;a href="https://dev.to/chriscmuir/just-my-notes-creating-a-basic-slack-app-pf1"&gt;previous blog&lt;/a&gt;, remember that the application runs on port 5000.  This is why I've set EXPOSE to 5000 in the Dockerfile.&lt;/p&gt;

&lt;p&gt;(4) When building the container we want it to download the node_modules fresh.  To avoid the existing node_modules being copied over, we create a &lt;em&gt;&lt;em&gt;.dockerignore&lt;/em&gt;&lt;/em&gt; file and add the following entries:&lt;/p&gt;


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

&lt;p&gt;node_modules&lt;br&gt;
.dockerignore&lt;/p&gt;

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

&lt;/div&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Build the Docker image&lt;br&gt;
&lt;/h1&gt;

&lt;p&gt;With the Dockerfile in place we can then build the first Docker application image using the following command from the application's source base directory:&lt;/p&gt;

&lt;p&gt;(5) &lt;code&gt;docker build -t scratchslackapp .&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The -t flag represents the image name to build in Docker.  + The image name must be in lowercase.&lt;/li&gt;
&lt;li&gt;The final full-stop implies the image is built from the current directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docker images can be listed by executing &lt;code&gt;docker images&lt;/code&gt; after the build.&lt;/p&gt;

&lt;h1&gt;
  
  
  Create and run a Docker container
&lt;/h1&gt;

&lt;p&gt;With the image in place, we can now create a running container based on the image:&lt;/p&gt;

&lt;p&gt;(6) &lt;code&gt;docker run --name scratchslackapp -p 5000:5000 -d scratchslackapp&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The --name flag defines the container name.  I've made this the same as the image, but it can be different.&lt;/li&gt;
&lt;li&gt;The -p flag defines the host post mapped to the container port we defined earlier in the Dockerfile.  I've kept this to 5000 in all cases to keep things simple.&lt;/li&gt;
&lt;li&gt;The -d flag runs the container in the background&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some useful additional Docker commands to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker ps&lt;/code&gt; - lists the running Docker containers, which includes a unique generated container-id per instance, used in the following commands.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker stop &amp;lt;container-id&lt;/code&gt; - stops a container&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker rm &amp;lt;container-name&amp;gt;&lt;/code&gt; - deletes a container&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker logs --follow &amp;lt;container-id&amp;gt;&lt;/code&gt; - tails the container's STDOUT &amp;amp; STDERR output to the screen.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the code needs to be updated, stop and remove the previous container, then build and run via the docker commands described above.&lt;/p&gt;

&lt;h1&gt;
  
  
  Accessing the Docker container via Slack
&lt;/h1&gt;

&lt;p&gt;From the above, assuming the Docker container is now running, from the previous blog post, remember if we've restarted ngrok, that the ngrok port must align with the Docker EXPOSE port, and, if we're using the free version of ngrok that rotates the URL, we must also update the URL in the Slack manifest file.  Look to the &lt;a href="https://dev.to/chriscmuir/just-my-notes-creating-a-basic-slack-app-pf1"&gt;previous blog post&lt;/a&gt; for how that was done. &lt;/p&gt;

</description>
      <category>node</category>
      <category>docker</category>
      <category>slack</category>
    </item>
    <item>
      <title>Creating a basic Slack app</title>
      <dc:creator>Chris Muir</dc:creator>
      <pubDate>Tue, 15 Jun 2021 05:53:54 +0000</pubDate>
      <link>https://dev.to/chriscmuir/just-my-notes-creating-a-basic-slack-app-pf1</link>
      <guid>https://dev.to/chriscmuir/just-my-notes-creating-a-basic-slack-app-pf1</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmwysuawi5izgpvmbfkhl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmwysuawi5izgpvmbfkhl.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Slack has done a good job of documenting how to create a basic Slack app to get you over the configuration hump.  And there are some handy simple examples on Glitch which save you from having to create your own server to host the backend code.  The following blog post captures my notes on how to do the same on my local Mac using NodeJS, Slack's &lt;a href="https://slack.dev/bolt-js/concepts" rel="noopener noreferrer"&gt;Bolt&lt;/a&gt; JavaScript framework, and ngrok to expose the application to the internet.&lt;/p&gt;

&lt;p&gt;There's nothing revolutionary in the following blog post, this is just a documentation exercise so I don't have to keep remembering every step!&lt;/p&gt;

&lt;h1&gt;
  
  
  Setup ngrok
&lt;/h1&gt;

&lt;p&gt;Mac install instructions:&lt;br&gt;
(1) Download ngrok (&lt;a href="https://ngrok.com/download" rel="noopener noreferrer"&gt;https://ngrok.com/download&lt;/a&gt;)&lt;br&gt;
(2) Unzip the ngrok zip file, and drag and drop the resulting ngrok file to our user's Mac Application folder&lt;br&gt;
(3) Setup a symbolic link so ngrok can be accessed across our system:&lt;br&gt;
&lt;code&gt;cd /usr/local/bin&lt;/code&gt;&lt;br&gt;
&lt;code&gt;ln -s /Applications/ngrok ngrok&lt;/code&gt;&lt;br&gt;
(4) Start ngrok:&lt;br&gt;
&lt;code&gt;ngrok http 5000&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Note we're forwarding to port 5000 in this example.  Change this to whatever we want, but record the value.  This will be important when we create the backend NodeJS code for the application shortly.&lt;/p&gt;

&lt;p&gt;From the ngrok's output note the https:// forwarding address.  Record this, you will need this shortly.  The following is an example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Session Status                online                                                                
Session Expires               1 hour, 59 minutes                                                    
Version                       2.3.40                                                                
Region                        United States (us)                                                    
Web Interface                 http://127.0.0.1:4040                                                 
Forwarding                    http://165a936dd19b.ngrok.io -&amp;gt; http://localhost:5000                 
Forwarding                    https://165a936dd19b.ngrok.io -&amp;gt; http://localhost:5000                
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Configure the app at api.slack.com
&lt;/h1&gt;

&lt;p&gt;Slack's &lt;a href="https://api.slack.com" rel="noopener noreferrer"&gt;api.slack.com&lt;/a&gt; website is where we setup and maintain the configuration of our application such as the application name, privileges, and the URLs for Slack to call our backend NodeJS application that we will create shortly.&lt;/p&gt;

&lt;p&gt;In the following example we will use Slack's new beta &lt;a href="https://api.slack.com/reference/manifests" rel="noopener noreferrer"&gt;manifest file&lt;/a&gt; format to setup the basic configuration.&lt;/p&gt;

&lt;p&gt;(5) Create or reuse our own Slack account and Slack workspace&lt;br&gt;
(6) Login to &lt;a href="https://api.slack.com" rel="noopener noreferrer"&gt;api.slack.com&lt;/a&gt; using our Slack account&lt;br&gt;
(7) Select &lt;em&gt;&lt;em&gt;Your Apps&lt;/em&gt;&lt;/em&gt; button top right, then the &lt;em&gt;&lt;em&gt;Create an App&lt;/em&gt;&lt;/em&gt; button centre of the screen&lt;br&gt;
(8) Select &lt;em&gt;&lt;em&gt;From an app manifest&lt;/em&gt;&lt;/em&gt;&lt;br&gt;
(9) Step 1 of 3: Select your workspace&lt;br&gt;
(10) Step 2 of 3: Copy in the following manifest 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="na"&gt;_metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;major_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;minor_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;display_information&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;ScratchSlackApp&lt;/span&gt;
&lt;span class="na"&gt;features&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app_home&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;home_tab_enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;messages_tab_enabled&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;messages_tab_read_only_enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;bot_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;display_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ScratchSlackApp&lt;/span&gt;
    &lt;span class="na"&gt;always_online&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;oauth_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;bot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;chat:write&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;im:write&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;im:history&lt;/span&gt;
&lt;span class="na"&gt;settings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;event_subscriptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;request_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://165a936dd19b.ngrok.io/slack/events&lt;/span&gt;
    &lt;span class="na"&gt;bot_events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;message.im&lt;/span&gt;
  &lt;span class="na"&gt;org_deploy_enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;socket_mode_enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the &lt;em&gt;&lt;em&gt;request_url&lt;/em&gt;&lt;/em&gt; with the ngrok https:// forwarding address from earlier.&lt;/p&gt;

&lt;p&gt;(11) Continue with step 3 of 3: Select &lt;em&gt;&lt;em&gt;Create&lt;/em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;(12) The website will now take us to the settings for the application.  Ensure the &lt;em&gt;&lt;em&gt;Basic Information&lt;/em&gt;&lt;/em&gt; settings page is selected, then within this page under the &lt;em&gt;&lt;em&gt;Install your app&lt;/em&gt;&lt;/em&gt; heading, select the &lt;em&gt;&lt;em&gt;Install to Workspace&lt;/em&gt;&lt;/em&gt; button.&lt;br&gt;
(13) In the proceeding permissions page, select the &lt;em&gt;&lt;em&gt;Allow&lt;/em&gt;&lt;/em&gt; button.  This step will make the application available to our user in the Slack client under the Apps section.  At this stage the application has no backend logic so the app will do nothing, we will create this next.&lt;br&gt;
(14) Returning to the &lt;em&gt;&lt;em&gt;Basic Information&lt;/em&gt;&lt;/em&gt; setting page, navigate down to the &lt;em&gt;&lt;em&gt;App Credentials&lt;/em&gt;&lt;/em&gt; section and &lt;em&gt;&lt;em&gt;Show the Signing Secret&lt;/em&gt;&lt;/em&gt; (commonly known as the 'Slack Signing Secret').  Record this value, we will need this value in a moment.&lt;br&gt;
(15) Select the &lt;em&gt;&lt;em&gt;OAuth &amp;amp; Permissions&lt;/em&gt;&lt;/em&gt; features page, and note the &lt;em&gt;&lt;em&gt;Bot User Oauth Token&lt;/em&gt;&lt;/em&gt;.  Also record this value, you will need this in a moment.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ngrok note: if you stop and restart ngrok, assuming you are using the free version which does not provide a static URL, the ngrok URL will change each restart.  To update the above request_url in the Slack app, visit the &lt;em&gt;&lt;em&gt;Event Subscriptions&lt;/em&gt;&lt;/em&gt; page and update the &lt;em&gt;&lt;em&gt;Request URL&lt;/em&gt;&lt;/em&gt;.  However note if you manually update this value (rather than defining it through the manifest file when you first create the app), Slack will attempt to check that the URL is live and running.  As such you need to create and run the Slack application first as defined in the next section before you can update the URL.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Create the NodeJS backend application
&lt;/h2&gt;

&lt;p&gt;On completing the above steps, we are now ready to create the backend NodeJS application that will listen and respond to events sent by the user via the Slack client, relayed through Slack, as configured in the above settings.&lt;/p&gt;

&lt;p&gt;In other words if the user types a message into our Slack app in the Slack client, the message will be relayed from the Slack client to Slack's own servers, whereupon Slack will look up the request_url defined above, and send the message to the defined URL where our Slack code is running and can respond.&lt;/p&gt;

&lt;p&gt;(16) On our local Mac:&lt;br&gt;
&lt;code&gt;cd ~/Desktop&lt;/code&gt;&lt;br&gt;
&lt;code&gt;mkdir ScratchSlackApp&lt;/code&gt;&lt;br&gt;
&lt;code&gt;cd ScratchSlackApp&lt;/code&gt;&lt;br&gt;
&lt;code&gt;npm init -y&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;(17) In the same directory open the generated &lt;em&gt;&lt;em&gt;package.json&lt;/em&gt;&lt;/em&gt; file in our favourite text editor and overwrite the complete file with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scratchslackapp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Error: no test specified&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; exit 1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ISC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@slack/bolt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.4.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dotenv"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^10.0.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(18) Still within our favourite text editor, create a new file &lt;em&gt;&lt;em&gt;app.js&lt;/em&gt;&lt;/em&gt; in the same directory and copy in the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ExpressReceiver&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;@slack/bolt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SLACK_BOT_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;signingSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SLACK_SIGNING_SECRET&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;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;say&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hi!&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;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="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⚡️ Bolt app is running on port &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The basic code here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;&lt;em&gt;New App()&lt;/em&gt;&lt;/em&gt; sets up a Slack app using the Slack Bolt JavaScript framework.  This includes all the magic for setting up the required HTTP listeners, that will receive HTTP/HTTPS requests sent from Slack to our code via the request_url we configured in the manifest file earlier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;em&gt;&lt;em&gt;app.message()&lt;/em&gt;&lt;/em&gt; handler receives 'text' messages from Slack which we can then respond to.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The final &lt;em&gt;&lt;em&gt;app.start()&lt;/em&gt;&lt;/em&gt; call in the async block starts the Bolt server, including the HTTP listeners.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(19) Note the 3 &lt;em&gt;&lt;em&gt;process.env&lt;/em&gt;&lt;/em&gt; calls to &lt;em&gt;&lt;em&gt;PORT, SLACK_BOT_TOKEN &amp;amp; SLACK_SIGNING_SECRET&lt;/em&gt;&lt;/em&gt;.  These are the 3 values we asked you to record earlier.  To set these in our favourite text editor create an additional file &lt;em&gt;&lt;em&gt;.env&lt;/em&gt;&lt;/em&gt; in the application's base directory, and copy in the values we recorded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PORT=5000
SLACK_BOT_TOKEN=xoxb-123472270484-1234804366771-cEXFXx0jgAA9bnAAaS16fvgc
SLACK_SIGNING_SECRET=263b7d12a7ccaea3f838f3ef123062ef
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above values are examples only.&lt;/p&gt;

&lt;p&gt;(20) Finally on the command line we need to install the libraries/dependencies defined in the &lt;em&gt;&lt;em&gt;package.json&lt;/em&gt;&lt;/em&gt; file then run our application:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install&lt;/code&gt;&lt;br&gt;
&lt;code&gt;node app.js&lt;/code&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>slack</category>
      <category>bolt</category>
    </item>
    <item>
      <title>fast-csv for CSV files</title>
      <dc:creator>Chris Muir</dc:creator>
      <pubDate>Fri, 05 Mar 2021 01:52:41 +0000</pubDate>
      <link>https://dev.to/chriscmuir/fast-csv-for-csv-files-21a1</link>
      <guid>https://dev.to/chriscmuir/fast-csv-for-csv-files-21a1</guid>
      <description>&lt;p&gt;I recently had to undertake pre-processing on a CSV file with NodeJS+Typescript before ingesting it into a system.&lt;/p&gt;

&lt;p&gt;The CSV file in question presents a number of challenges:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The CSV file is large @ ~125k rows&lt;/li&gt;
&lt;li&gt;Includes a header row but individual headers need to be renamed&lt;/li&gt;
&lt;li&gt;There are redundant columns to remove &lt;/li&gt;
&lt;li&gt;There may be additional columns that we also don't know about that need to be dropped&lt;/li&gt;
&lt;li&gt;The columns need reordering&lt;/li&gt;
&lt;li&gt;Blank lines must be skipped&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Via a quick Google I found &lt;a href="https://www.npmjs.com/package/fast-csv"&gt;fast-csv&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An initial &amp;amp; superficial look at fast-csv highlights a few qualities making it attractive enough to explore further:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is still actively being developed (at the time of this post) giving some assurance around bug fixes&lt;/li&gt;
&lt;li&gt;Uses the MIT friendly open source license&lt;/li&gt;
&lt;li&gt;Has no runtime dependencies minimizing any down stream license issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In looking at the feature set, fast-csv is comprised of  'parse' and 'format' routines for ingesting and transforming CSV files. It also supports streams for fast processing of large files. The following describes how I made use of fast-csv features to meet the above requirements.&lt;/p&gt;

&lt;p&gt;To start with here's the initial CSV file we will ingest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;beta,alpha,redundant,charlie,delta

betaRow1,alphaRow1,redundantRow1,charlieRow1,deltaRow1
betaRow2,alphaRow2,redundantRow2,charlieRow2,deltaRow2
betaRow3,alphaRow3,redundantRow3,charlieRow3,deltaRow3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our goal is to rename and reorder the columns, drop the blank line, drop the 'redundant' column, and our program should be able to also drop the 'delta' column which it wont know about at all.  The final output should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NewAlpha,NewBeta,NewCharlie
alphaRow1,betaRow1,charlieRow1
alphaRow2,betaRow2,charlieRow2
alphaRow3,betaRow3,charlieRow3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following code shows the solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;csv&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;fast-csv&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;inputFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;__dirname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/../sample-data/input.csv&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;outputFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;__dirname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/../sample-data/output.csv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writeStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createWriteStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputFile&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;parse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="na"&gt;ignoreEmpty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;discardUnmappedColumns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;beta&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alpha&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redundant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;charlie&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;transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;NewAlpha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// reordered&lt;/span&gt;
        &lt;span class="na"&gt;NewBeta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;NewCharlie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charlie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// redundant is dropped&lt;/span&gt;
        &lt;span class="c1"&gt;// delta is not loaded by parse() above&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;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;writeStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In explaining the solution:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;parse() options&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ignoreEmpty takes care of skipping the blank line(s)&lt;/li&gt;
&lt;li&gt;discardUnmappedColumns will drop any columns we don't specify in the following headers option, taking care of dropping the 'delta' column&lt;/li&gt;
&lt;li&gt;headers maps the columns we are loading. Note how I've used discardUnmappedColumns to drop 'delta' but I'm still loading 'redundant'. The 'redundant' column is dropped in the format() options described next&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;format() options&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;headers directs the output to include the header row&lt;/li&gt;
&lt;li&gt;The transform() row post-processor allows us to reorder the columns, rename the columns, and also drop the 'redundant' column&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a larger CSV file in hand, testing shows the above routine can process ~125k rows with 126 columns, from a file of approx 135MB in size, in ~19 seconds on my MBP 3.2Ghz i7.&lt;/p&gt;

&lt;p&gt;fast-csv indeed.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>node</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
