<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Benny Code</title>
    <description>The latest articles on Forem by Benny Code (@bennycode).</description>
    <link>https://forem.com/bennycode</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%2F29391%2F96ca4aac-a6d4-469e-b402-50ef67b4de0c.png</url>
      <title>Forem: Benny Code</title>
      <link>https://forem.com/bennycode</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bennycode"/>
    <language>en</language>
    <item>
      <title>How I Save $1,463 per Month Using Claude Code as My Server Admin</title>
      <dc:creator>Benny Code</dc:creator>
      <pubDate>Tue, 07 Apr 2026 20:35:44 +0000</pubDate>
      <link>https://forem.com/bennycode/how-i-save-1463-per-month-using-claude-code-as-my-server-admin-1pdb</link>
      <guid>https://forem.com/bennycode/how-i-save-1463-per-month-using-claude-code-as-my-server-admin-1pdb</guid>
      <description>

&lt;p&gt;Heroku was great when I started. Push code, get a URL, done. Platform-as-a-Service won the last decade because it took operational complexity away for a surcharge, and that trade-off made sense when managing servers meant writing Ansible playbooks and debugging iptables rules. But the landscape has changed. With AI coding tools like &lt;a href="https://docs.anthropic.com/en/docs/claude-code/overview" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;, the complexity of managing your own server largely goes away. You describe what you want in plain English, and the tool SSHs in and runs the commands. That shifts the equation: owning your servers becomes very attractive again when the operational overhead drops to near zero.&lt;/p&gt;

&lt;p&gt;I run several Node.js services built on TypeScript: web apps, APIs, and background workers like Telegram bots. Over time, the Heroku bills kept climbing. A basic dyno, a Postgres add-on, and a couple of worker processes added up to hundreds of dollars per year for apps that could run on a single server. I decided to migrate everything to a &lt;a href="https://www.hetzner.com/" rel="noopener noreferrer"&gt;Hetzner&lt;/a&gt; dedicated server running &lt;a href="https://dokku.com/" rel="noopener noreferrer"&gt;Dokku&lt;/a&gt;, and the entire setup happened in the background through Claude Code's CLI while I was busy building new TypeScript projects. Here is exactly how it was done, so you can use it as a blueprint for your own migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Dokku
&lt;/h2&gt;

&lt;p&gt;Dokku is a lightweight, open-source Platform-as-a-Service (PaaS) built on Docker. Under the hood, it uses &lt;a href="https://github.com/gliderlabs/herokuish" rel="noopener noreferrer"&gt;Herokuish&lt;/a&gt; to run Heroku buildpacks, which means your existing &lt;code&gt;Procfile&lt;/code&gt; and &lt;code&gt;package.json&lt;/code&gt; work without changes. If your app runs on Heroku, it runs on Dokku.&lt;/p&gt;

&lt;p&gt;Even though I migrated away from Heroku, this setup is not limited to Heroku. I also moved services from &lt;a href="https://render.com/" rel="noopener noreferrer"&gt;Render&lt;/a&gt;, &lt;a href="https://railway.app/" rel="noopener noreferrer"&gt;Railway&lt;/a&gt;, &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;, and &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;. Any service that deploys from a &lt;code&gt;Procfile&lt;/code&gt; or a Dockerfile works out of the box. The difference is that Dokku runs on your own server, so you pay for hardware instead of per-platform pricing.&lt;/p&gt;

&lt;p&gt;I considered other tools like &lt;a href="https://coolify.io/" rel="noopener noreferrer"&gt;Coolify&lt;/a&gt; and &lt;a href="https://caprover.com/" rel="noopener noreferrer"&gt;CapRover&lt;/a&gt;, but Dokku won because of its simplicity. There is no web UI to maintain, no dashboard to secure, just a CLI that works over SSH. This also means less attack surface compared to something like Coolify, which exposes an admin panel on a port that needs to be locked down. If you prefer a web interface, Coolify is worth looking at, but I like the Unix philosophy of Dokku: it does one thing well.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Plot Twist: I Didn't Write a Single Command
&lt;/h2&gt;

&lt;p&gt;Before I walk you through the setup, I should confess something. Every command you see in this article, every &lt;code&gt;dokku&lt;/code&gt; call, every &lt;code&gt;gh api&lt;/code&gt; invocation, every &lt;code&gt;ssh&lt;/code&gt; session, I did not type any of them. Not most of them. None of them. The entire migration was done through &lt;a href="https://docs.anthropic.com/en/docs/claude-code/overview" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; running in my terminal. I described what I wanted in plain English, and Claude Code SSHed into the server and executed everything.&lt;/p&gt;

&lt;p&gt;The workflow is simple: I created a private GitHub repo called &lt;code&gt;hetzner-server&lt;/code&gt; and told Claude Code to use it as "infrastructure as docs", a place where every server configuration, domain change, and deployment setup gets documented in a &lt;code&gt;README.md&lt;/code&gt;. When I start a new Claude Code session from that repo, it reads the existing notes and knows the full setup: which apps are deployed, which domains point where, which env vars are set, and how the webhook pipeline works.&lt;/p&gt;

&lt;p&gt;When I want to deploy a new app, I just tell Claude Code "deploy this GitHub repo to my server." It SSHs into the Hetzner box, creates the Dokku app, uses the &lt;code&gt;gh&lt;/code&gt; CLI to set up the webhook on the repo, configures the deploy key for private repos, enables Let's Encrypt, and updates the documentation. The entire deploy-a-new-app flow is a single conversation.&lt;/p&gt;

&lt;p&gt;This works because Dokku and the GitHub CLI are both command-line tools. Claude Code can SSH into the server and run &lt;code&gt;dokku&lt;/code&gt; commands, and it can run &lt;code&gt;gh api&lt;/code&gt; locally to configure webhooks and deploy keys. There is no need for a server-side MCP or API. The server is just a Linux box that accepts SSH connections, and Claude Code is the glue that ties everything together. Some providers like &lt;a href="https://www.hostinger.com/" rel="noopener noreferrer"&gt;Hostinger&lt;/a&gt; offer an &lt;a href="https://www.hostinger.com/support/11079316-hostinger-api-mcp-server/" rel="noopener noreferrer"&gt;API MCP server&lt;/a&gt;, but for a dedicated server you don't need one. SSH is enough.&lt;/p&gt;

&lt;p&gt;Now, here is everything Claude Code did for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Server
&lt;/h2&gt;

&lt;p&gt;I ordered a Hetzner AX41-NVMe dedicated server (AMD Ryzen 5 3600 6-Core, two 512GB NVMe drives, 64GB RAM). From there, Claude Code took over. It SSHed into the rescue system, installed Ubuntu 24.04 LTS via Hetzner's &lt;code&gt;installimage&lt;/code&gt;, and configured RAID 1 for redundancy.&lt;/p&gt;

&lt;p&gt;Once Ubuntu was running, it installed Dokku:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget &lt;span class="nt"&gt;-NP&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; https://dokku.com/bootstrap.sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;bash bootstrap.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, it added my SSH key so I could deploy via &lt;code&gt;git push&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'YOUR_PUBLIC_SSH_KEY'&lt;/span&gt; | dokku ssh-keys:add admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entire server setup, from ordering to a running Dokku instance, took Claude Code about 20 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a Domain
&lt;/h2&gt;

&lt;p&gt;Dokku assigns apps a subdomain automatically using &lt;a href="https://sslip.io/" rel="noopener noreferrer"&gt;sslip.io&lt;/a&gt;, a free service that resolves any IP embedded in a hostname back to that IP. For example, &lt;code&gt;95.217.148.47.sslip.io&lt;/code&gt; resolves to &lt;code&gt;95.217.148.47&lt;/code&gt;. This is great for testing, but for production you want a real domain.&lt;/p&gt;

&lt;p&gt;I bought my domain on &lt;a href="https://www.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare&lt;/a&gt; because they offer an &lt;a href="https://developers.cloudflare.com/agents/model-context-protocol/mcp-servers-for-cloudflare/" rel="noopener noreferrer"&gt;official MCP server&lt;/a&gt; and API tokens with fine-grained permissions. Cloudflare provides an "Edit zone DNS" token template that grants only DNS access, nothing else. This means AI coding tools like &lt;a href="https://docs.anthropic.com/en/docs/claude-code/overview" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; can manage DNS records programmatically without having access to your billing, account settings, or other services. If you already have domains on another registrar like &lt;a href="https://www.gandi.net/" rel="noopener noreferrer"&gt;Gandi&lt;/a&gt;, that works too since they have a REST API with Personal Access Tokens.&lt;/p&gt;

&lt;p&gt;Set up DNS by pointing your domain to the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# A record&lt;/span&gt;
yourdomain.com → YOUR_SERVER_IP

&lt;span class="c"&gt;# AAAA record (if you have IPv6)&lt;/span&gt;
yourdomain.com → YOUR_SERVER_IPV6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then configure Dokku to use your domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku domains:set-global yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SSL Certificates
&lt;/h2&gt;

&lt;p&gt;Dokku has a &lt;a href="https://github.com/dokku/dokku-letsencrypt" rel="noopener noreferrer"&gt;Let's Encrypt plugin&lt;/a&gt; that handles SSL certificates automatically. Install it once and enable it per app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
dokku letsencrypt:set &lt;span class="nt"&gt;--global&lt;/span&gt; email you@example.com
dokku letsencrypt:enable your-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin handles certificate renewal automatically. For services that aren't Dokku apps (like a landing page or a webhook listener), you can use &lt;a href="https://certbot.eff.org/" rel="noopener noreferrer"&gt;certbot&lt;/a&gt; directly with nginx.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying Your First App
&lt;/h2&gt;

&lt;p&gt;If your app already has a &lt;code&gt;Procfile&lt;/code&gt;, deploying to Dokku is almost identical to deploying to Heroku:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku apps:create my-app
git remote add dokku dokku@YOUR_SERVER_IP:my-app
git push dokku main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dokku detects the language from your &lt;code&gt;package.json&lt;/code&gt;, installs dependencies, runs &lt;code&gt;npm run build&lt;/code&gt;, and starts the app using the command in your &lt;code&gt;Procfile&lt;/code&gt;. For Node.js, the version is resolved from &lt;code&gt;engines.node&lt;/code&gt; in your &lt;code&gt;package.json&lt;/code&gt;, or from an &lt;code&gt;.nvmrc&lt;/code&gt; file in the repo root. If neither is present, it defaults to the latest version.&lt;/p&gt;

&lt;p&gt;For worker processes (like a Telegram bot or background job), scale down the web process and scale up the worker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku ps:scale my-app &lt;span class="nv"&gt;web&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  GitHub Auto-Deploy with Webhooks
&lt;/h2&gt;

&lt;p&gt;On Heroku, connecting a GitHub repo for auto-deploy is a checkbox. On Dokku, you need to set it up yourself, but it is straightforward. I used &lt;a href="https://github.com/adnanh/webhook" rel="noopener noreferrer"&gt;adnanh/webhook&lt;/a&gt;, a lightweight webhook listener that runs as a systemd service.&lt;/p&gt;

&lt;p&gt;Install it on the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; webhook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a deploy script that clones the repo and pushes to Dokku:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;APP_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;CLONE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;REF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;BRANCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REF&lt;/span&gt;&lt;span class="p"&gt;#refs/heads/&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Create app if it does not exist&lt;/span&gt;
dokku apps:list | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"^&lt;/span&gt;&lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;&lt;span class="s2"&gt;$"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; dokku apps:create &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Convert HTTPS URL to SSH for private repo access&lt;/span&gt;
&lt;span class="nv"&gt;SSH_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLONE_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s|https://github.com/|git@github.com:|"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

dokku git:sync &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure the webhook with HMAC signature validation so only GitHub can trigger deploys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dokku-deploy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"execute-command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/opt/webhook/deploy.sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"command-working-directory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/tmp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"pass-arguments-to-command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"repository.name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"repository.clone_url"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ref"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"trigger-rule"&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;"and"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"match"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payload-hmac-sha256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"secret"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_WEBHOOK_SECRET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"parameter"&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;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"X-Hub-Signature-256"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"match"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"refs/heads/main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"parameter"&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;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ref"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;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;Run it as a systemd service, then add the webhook URL to your GitHub repo. You can do this from the GitHub UI or with the &lt;code&gt;gh&lt;/code&gt; CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh api repos/OWNER/REPO/hooks &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;--input&lt;/span&gt; - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "config": {
    "url": "https://yourdomain.com/hooks/dokku-deploy",
    "content_type": "json",
    "secret": "YOUR_WEBHOOK_SECRET",
    "insecure_ssl": "0"
  },
  "events": ["push"],
  "active": true
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For private repos, generate an SSH deploy key on the server and add it to the repo's deploy keys. GitHub requires a unique key per repo, so if you have multiple private repos, each one needs its own key pair.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating a Postgres Database from Heroku
&lt;/h2&gt;

&lt;p&gt;This is the part that sounds scary but is actually simple. You need the &lt;a href="https://devcenter.heroku.com/articles/heroku-cli" rel="noopener noreferrer"&gt;Heroku CLI&lt;/a&gt; and the Dokku Postgres plugin.&lt;/p&gt;

&lt;p&gt;Install the Postgres plugin on your server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a database and link it to your app. Dokku automatically sets the &lt;code&gt;DATABASE_URL&lt;/code&gt; environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku postgres:create my-app-db
dokku postgres:link my-app-db my-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now capture a backup from Heroku, download it to the server, and import it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Capture a fresh backup&lt;/span&gt;
heroku pg:backups:capture &lt;span class="nt"&gt;--app&lt;/span&gt; my-heroku-app

&lt;span class="c"&gt;# Get the download URL&lt;/span&gt;
heroku pg:backups:url &lt;span class="nt"&gt;--app&lt;/span&gt; my-heroku-app

&lt;span class="c"&gt;# Download the dump on the server&lt;/span&gt;
ssh root@YOUR_SERVER_IP &lt;span class="s1"&gt;'curl -o /tmp/heroku.dump "BACKUP_URL_HERE"'&lt;/span&gt;

&lt;span class="c"&gt;# Import into Dokku Postgres&lt;/span&gt;
ssh root@YOUR_SERVER_IP &lt;span class="s1"&gt;'cat /tmp/heroku.dump | dokku postgres:import my-app-db'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy over your environment variables from Heroku (excluding &lt;code&gt;DATABASE_URL&lt;/code&gt;, which Dokku sets automatically):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# View Heroku config&lt;/span&gt;
heroku config &lt;span class="nt"&gt;--app&lt;/span&gt; my-heroku-app

&lt;span class="c"&gt;# Set on Dokku (skip DATABASE_URL)&lt;/span&gt;
dokku config:set my-app &lt;span class="nv"&gt;KEY1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;value1 &lt;span class="nv"&gt;KEY2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;value2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the migration by comparing record counts between Heroku and your new server. Before scaling down the Heroku dynos, make sure your DNS A records already point to your dedicated server and that the change has fully propagated. DNS records have TTLs (time-to-live), so changing an address is not immediate. If you scale down Heroku while DNS is still pointing there, your users will see errors until propagation completes. Lower your TTL ahead of time and verify with &lt;code&gt;dig yourdomain.com A&lt;/code&gt; before proceeding.&lt;/p&gt;

&lt;p&gt;Once DNS is confirmed and you are confident everything works on Hetzner, scale the Heroku app to zero dynos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku ps:scale &lt;span class="nv"&gt;web&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;--app&lt;/span&gt; my-heroku-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a few days of running in production on Hetzner, you can delete the Heroku app entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku apps:destroy my-heroku-app &lt;span class="nt"&gt;--confirm&lt;/span&gt; my-heroku-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Managing Repo Settings with the GitHub CLI
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://cli.github.com/" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&gt; (&lt;code&gt;gh&lt;/code&gt;) is essential for this workflow. Claude Code used it to create webhooks on repos, add deploy keys for private repo access, manage branch protection rules, and close stale pull requests. Everything that you would click through in the GitHub UI can be scripted with &lt;code&gt;gh api&lt;/code&gt;. For example, listing all webhook deliveries to debug a failed deploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh api repos/OWNER/REPO/hooks/HOOK_ID/deliveries &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--jq&lt;/span&gt; &lt;span class="s1"&gt;'.[] | {delivered_at, status, status_code}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What It Costs
&lt;/h2&gt;

&lt;p&gt;On Heroku, a "Basic" always-on dyno gives you 512 MB of RAM and costs $7 per month. Each Essential Postgres database adds another $5 per month. My Hetzner AX41-NVMe server has 64 GB of RAM, two 512 GB NVMe drives, and costs $52.24 per month (hosted in Germany, taxes included).&lt;/p&gt;

&lt;p&gt;To get the same 64 GB of RAM on Heroku, you would need 128 Basic dynos. If each app also needs its own Postgres database, the math looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Heroku&lt;/th&gt;
&lt;th&gt;Hetzner + Claude Code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;128 dynos ("Basic" model, 512 MB each)&lt;/td&gt;
&lt;td&gt;$896/mo&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;128 Postgres databases ("Essential" model, 1 GB storage each)&lt;/td&gt;
&lt;td&gt;$640/mo&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dedicated server ("AX41-NVMe" model, 64 GB RAM, 512 GB storage)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;$52.24/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code subscription&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;$20/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1,536/mo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$72.24/mo&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is a 21x price difference for the same amount of RAM and more than enough disk space for 128 small databases. Of course, this comparison only holds when looking at RAM and storage. In practice, CPU will be the bottleneck long before you hit 128 apps on a single machine. A dedicated server is a monolith: you cannot horizontally scale it by adding more instances behind a load balancer the way Heroku dynos can. The same limitation applies to the databases, which all share the same Postgres instance on one machine.&lt;/p&gt;

&lt;p&gt;Dokku actually handles more than you might expect. Zero-downtime deployments work out of the box: it starts the new container, runs healthchecks, switches nginx traffic over, and shuts down the old container after 60 seconds. Load balancing is also built in: scale an app to multiple containers with &lt;code&gt;dokku ps:scale web=3&lt;/code&gt; and nginx automatically distributes traffic across them. This makes sense even on a single server since Node.js is single-threaded per process, so multiple containers let you use multiple CPU cores.&lt;/p&gt;

&lt;p&gt;For DDoS mitigation, bot protection, and WAF (Web Application Firewall), you can enable the Cloudflare proxy on your DNS records. When proxied, all traffic goes through Cloudflare before reaching your server, which hides your server's real IP and gives you DDoS protection on the free tier. Turning it on is a single toggle per DNS record. Auto-scaling is the one thing you genuinely lose compared to Heroku since Dokku only supports manual scaling.&lt;/p&gt;

&lt;p&gt;That said, for demos, side projects, and low-traffic production services, a single Hetzner server with Dokku is the perfect sweet spot. You get the Heroku developer experience at a fixed monthly cost, and you only start thinking about scaling when your apps actually need it.&lt;/p&gt;

&lt;p&gt;This easy math works for me as an individual running personal projects. At company scale, the effect can become even larger. David Heinemeier Hansson (DHH), co-founder of &lt;a href="https://37signals.com/" rel="noopener noreferrer"&gt;37signals&lt;/a&gt; (the company behind Basecamp and HEY), wrote about their decision to leave the cloud entirely. In his post &lt;a href="https://world.hey.com/dhh/we-have-left-the-cloud-251760fb" rel="noopener noreferrer"&gt;"We have left the cloud"&lt;/a&gt;, he shared that "the napkin math is that we'll save at least $1.5 million per year by owning our own hardware rather than renting it from Amazon." That was posted back in June 2023, before models like Claude Opus 4.6 and MCP servers for major cloud hosting providers even existed. The cost of managing your own servers has only gotten cheaper since then.&lt;/p&gt;

&lt;p&gt;DHH and 37signals also built &lt;a href="https://github.com/basecamp/kamal" rel="noopener noreferrer"&gt;Kamal&lt;/a&gt;, an open-source deployment tool that handles zero-downtime deployments across multiple servers. If you outgrow a single-server Dokku setup and need something production-grade with rolling deploys, Kamal is worth looking at as the next step up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tools That Made It Work
&lt;/h2&gt;

&lt;p&gt;Here is a summary of everything used in this migration:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dokku.com/" rel="noopener noreferrer"&gt;Dokku&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;PaaS on your own server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/gliderlabs/herokuish" rel="noopener noreferrer"&gt;Herokuish&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Heroku buildpack compatibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/dokku/dokku-postgres" rel="noopener noreferrer"&gt;dokku-postgres&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Managed Postgres for Dokku&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/dokku/dokku-letsencrypt" rel="noopener noreferrer"&gt;dokku-letsencrypt&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Automatic SSL certificates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/adnanh/webhook" rel="noopener noreferrer"&gt;adnanh/webhook&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;GitHub webhook listener for auto-deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://certbot.eff.org/" rel="noopener noreferrer"&gt;certbot&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;SSL for non-Dokku services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://devcenter.heroku.com/articles/heroku-cli" rel="noopener noreferrer"&gt;Heroku CLI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Database export and app management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cli.github.com/" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Webhook setup and repo management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://sslip.io/" rel="noopener noreferrer"&gt;sslip.io&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Free wildcard DNS for testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docs.anthropic.com/en/docs/claude-code/overview" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;CLI-based AI assistant for server management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Domain registration and DNS with API/MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The entire migration was done over SSH with bash commands. No special server API or MCP integration was needed for the Hetzner server itself. You SSH in, run commands, and you are done. The only APIs I used were Cloudflare (for DNS), Gandi (for DNS on other domains), GitHub (for webhooks and deploy keys), and Heroku (for database export).&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping Everything Up to Date
&lt;/h2&gt;

&lt;p&gt;Setting up a server is only half the job. You also need to maintain it: install security patches, update system packages, renew certificates, and keep your application dependencies current. On Heroku, this mostly happens invisibly, though not always. You still need to manually approve &lt;a href="https://devcenter.heroku.com/articles/stack" rel="noopener noreferrer"&gt;Heroku Stack&lt;/a&gt; (base image) upgrades when they roll out, and PaaS providers don't always give you the latest and greatest environment since they have to ensure compatibility and maturity across their platform. On your own server, updates are fully your responsibility, but you also get to choose exactly when and what to upgrade.&lt;/p&gt;

&lt;p&gt;One option is to use a "managed server" from providers like Hetzner or &lt;a href="https://www.ovhcloud.com/" rel="noopener noreferrer"&gt;OVH&lt;/a&gt;, where the hosting company handles OS updates and security patches for you. But if you are running an unmanaged dedicated server like I am, you need a maintenance strategy. The first line of defense is enabling &lt;code&gt;unattended-upgrades&lt;/code&gt;, which automatically installs critical security patches daily without any manual intervention:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; unattended-upgrades
dpkg-reconfigure &lt;span class="nt"&gt;-f&lt;/span&gt; noninteractive unattended-upgrades
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For everything beyond OS-level patches, like updating Dokku plugins, checking app health, and keeping dependencies current, you can automate with Claude Code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.anthropic.com/en/docs/claude-code/scheduled-tasks" rel="noopener noreferrer"&gt;Claude Code Scheduled Tasks&lt;/a&gt; solve this nicely. They allow you to automate workflows by running prompts on a recurring basis, daily, weekly, or hourly. You can set up a scheduled task that SSHes into your server, runs &lt;code&gt;apt update &amp;amp;&amp;amp; apt upgrade&lt;/code&gt;, checks for Dokku plugin updates, verifies that all apps are healthy, and commits the results to your infrastructure-as-docs repo. It turns server maintenance into a background process that runs on autopilot, just like the initial setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;When you run multiple apps on one server, a compromised app should not be able to reach the others. Dokku runs each app in its own Docker container, which provides basic process isolation. But by default, containers still have broad Linux capabilities that an attacker could exploit. You can lock this down with a few Docker security flags that Dokku lets you set per app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku docker-options:add my-app deploy &lt;span class="s2"&gt;"--cap-drop=ALL"&lt;/span&gt;
dokku docker-options:add my-app deploy &lt;span class="s2"&gt;"--cap-add=NET_BIND_SERVICE"&lt;/span&gt;
dokku docker-options:add my-app deploy &lt;span class="s2"&gt;"--security-opt=no-new-privileges"&lt;/span&gt;
dokku ps:rebuild my-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--cap-drop=ALL&lt;/code&gt; removes all Linux capabilities from the container, so even if an attacker gets code execution, they cannot mount filesystems, modify network settings, or load kernel modules. &lt;code&gt;--cap-add=NET_BIND_SERVICE&lt;/code&gt; adds back only the one capability a web app actually needs: binding to a port. &lt;code&gt;--security-opt=no-new-privileges&lt;/code&gt; prevents processes inside the container from gaining elevated permissions through setuid binaries.&lt;/p&gt;

&lt;p&gt;This is not full VM-level isolation. All containers still share the same host kernel and Docker daemon. But it significantly raises the bar for an attacker trying to move laterally from one compromised app to another.&lt;/p&gt;

&lt;p&gt;Beyond container hardening, you should also lock down the server itself. Enable UFW (Uncomplicated Firewall) to only allow ports 22, 80, and 443, blocking everything else from the outside. Install &lt;a href="https://github.com/fail2ban/fail2ban" rel="noopener noreferrer"&gt;fail2ban&lt;/a&gt; to automatically ban IPs that attempt SSH brute-force attacks. And if your domain is on Cloudflare, enable the proxy (orange cloud) on your DNS records to get free DDoS mitigation and hide your server's real IP address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Firewall: only allow SSH, HTTP, HTTPS&lt;/span&gt;
ufw default deny incoming
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw &lt;span class="nb"&gt;enable&lt;/span&gt;

&lt;span class="c"&gt;# Fail2ban: ban after 5 failed SSH attempts&lt;/span&gt;
apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For personal projects and low-traffic services, these measures combined with container hardening are a practical and solid security baseline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy My Setup
&lt;/h2&gt;

&lt;p&gt;This article is long. It involves server configuration, webhook scripts, database migrations, SSL certificates, and dozens of bash commands. I didn't write any of it. And you don't have to either. If you want to replicate this setup, just give Claude Code this article to read and tell it to set up your server the same way. It will SSH in, install Dokku, configure your domains, set up the webhook pipeline, migrate your databases, and document everything in a repo for you. The entire blueprint is right here.&lt;/p&gt;

&lt;p&gt;If your Heroku bill is growing and your apps don't need Heroku's managed ecosystem, a dedicated server with Dokku gives you the same developer experience at a fraction of the cost.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>selfhosting</category>
      <category>typescript</category>
      <category>ai</category>
    </item>
    <item>
      <title>Why I Don’t Like Path Aliases in TypeScript</title>
      <dc:creator>Benny Code</dc:creator>
      <pubDate>Thu, 28 Aug 2025 17:15:09 +0000</pubDate>
      <link>https://forem.com/bennycode/why-i-dont-like-path-aliases-in-typescript-2b2a</link>
      <guid>https://forem.com/bennycode/why-i-dont-like-path-aliases-in-typescript-2b2a</guid>
      <description>&lt;p&gt;If you’ve been around modern TypeScript projects, you’ve probably seen imports like this:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;formatDate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/utils/date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of this:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;formatDate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../utils/date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That little &lt;code&gt;@/&lt;/code&gt; is a &lt;strong&gt;path alias&lt;/strong&gt;. It’s a shorthand you can configure in &lt;code&gt;tsconfig.json&lt;/code&gt; so that you don’t need to count how many &lt;code&gt;../&lt;/code&gt; levels you’re in when importing files.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are path aliases?
&lt;/h2&gt;

&lt;p&gt;In TypeScript, you enable them like this:&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;"compilerOptions"&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;"baseUrl"&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;"paths"&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;"@/*"&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="s2"&gt;"src/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;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;Now, instead of messy relative paths, you can just import from a clean root-based alias. Looks nice, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem they solve (and why I think it’s outdated)
&lt;/h2&gt;

&lt;p&gt;Path aliases exist to save you from typing &lt;code&gt;../../../../&lt;/code&gt; in imports. Fair enough but here’s the thing: &lt;strong&gt;modern IDEs already solved that problem.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In editors like VS Code, IntelliJ, and others, autocomplete instantly suggests the correct import path. When you move files around, the imports update automatically without you touching them. Even if the path happens to be long and messy, it doesn’t matter, because you never really type it by hand anyway.&lt;/p&gt;

&lt;p&gt;So the “typing &lt;code&gt;../..&lt;/code&gt; is painful” argument feels like a relic from 2015.&lt;/p&gt;

&lt;h2&gt;
  
  
  But don’t imports look messy?
&lt;/h2&gt;

&lt;p&gt;Not really because &lt;strong&gt;we barely look at them&lt;/strong&gt;. In editors like VS Code, imports collapse to a single line by default. Unless you’re adding a new one, or reviewing a diff, they’re out of sight, out of mind.&lt;/p&gt;

&lt;p&gt;So whether the import says:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;foo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../../../utils/foo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;foo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/utils/foo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... doesn’t matter during normal coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  When aliases are useful (for me)
&lt;/h2&gt;

&lt;p&gt;I’ll admit that aliases &lt;em&gt;are&lt;/em&gt; nice in one situation: &lt;strong&gt;copy-pasting code into docs, blog posts, or websites&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A code snippet with:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;formatDate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/utils/date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;looks much friendlier to readers than:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;formatDate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../../../../utils/date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When someone doesn’t have the full context of your app’s folder structure, the alias makes the snippet cleaner.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden danger of path aliases
&lt;/h2&gt;

&lt;p&gt;Here’s the part alias fans don’t like to talk about:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Path aliases don’t actually work in compiled JavaScript.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TypeScript only knows about them because of &lt;code&gt;tsconfig.json&lt;/code&gt;. When you compile your code, your output might still contain &lt;code&gt;@/utils&lt;/code&gt; and Node.js or the browser has no idea what that means.&lt;/p&gt;

&lt;p&gt;That means you now &lt;em&gt;must&lt;/em&gt; use a bundler (Webpack, Rollup, tsup, Vite, etc.) or a tool like &lt;code&gt;tsc-alias&lt;/code&gt; to rewrite paths at build time. If you forget, your published package or server build will break with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Cannot find module '@/utils'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, &lt;strong&gt;you introduced extra tooling complexity to solve a problem that wasn’t really a problem anymore.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>It's Easy To Switch from CommonJS to ES Modules with TS2ESM</title>
      <dc:creator>Benny Code</dc:creator>
      <pubDate>Mon, 30 Oct 2023 16:23:12 +0000</pubDate>
      <link>https://forem.com/typescripttv/its-easy-to-switch-from-commonjs-to-es-modules-with-ts2esm-4chg</link>
      <guid>https://forem.com/typescripttv/its-easy-to-switch-from-commonjs-to-es-modules-with-ts2esm-4chg</guid>
      <description>&lt;p&gt;Migrating a TypeScript project from CommonJS to ESM is now incredibly simple, thanks to the &lt;a href="https://github.com/bennycode/ts2esm"&gt;TS2ESM tool&lt;/a&gt; and the &lt;a href="https://devblogs.microsoft.com/typescript/announcing-typescript-5-2/#breaking-changes-and-correctness-fixes"&gt;configuration fixes in TS 5.2&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;You can achieve this in just 5 simple steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set &lt;code&gt;type&lt;/code&gt; to &lt;code&gt;module&lt;/code&gt; in your package.json&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;module&lt;/code&gt; to &lt;code&gt;nodenext&lt;/code&gt; in your tsconfig.json&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;moduleResolution&lt;/code&gt; to &lt;code&gt;nodenext&lt;/code&gt; in your tsconfig.json&lt;/li&gt;
&lt;li&gt;Globally install &lt;code&gt;ts2esm&lt;/code&gt; by running &lt;code&gt;npm i -g ts2esm&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Execute the &lt;code&gt;ts2esm&lt;/code&gt; command within your project's directory&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a result, you'll see that all your relative import and export statements now either include an explicit ".js" extension or "index.js" suffix, as is required for ECMAScript modules. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ts2esm&lt;/code&gt; command will also enhance imports from JSON files in your TypeScript code by using &lt;a href="https://v8.dev/features/import-assertions"&gt;import assertions&lt;/a&gt;:&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="nx"&gt;fixtures&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;../test/fixtures.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

</description>
      <category>typescript</category>
      <category>node</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>TypeChat: Eliminating Hallucinations in AI with TypeScript</title>
      <dc:creator>Benny Code</dc:creator>
      <pubDate>Tue, 01 Aug 2023 17:00:00 +0000</pubDate>
      <link>https://forem.com/typescripttv/typechat-eliminating-hallucinations-in-ai-with-typescript-4j5j</link>
      <guid>https://forem.com/typescripttv/typechat-eliminating-hallucinations-in-ai-with-typescript-4j5j</guid>
      <description>&lt;p&gt;Modern AI systems face a challenge called "hallucinations", where a Large Language Model (LLM) generates made-up responses to prompts. Even &lt;strong&gt;ChatGPT&lt;/strong&gt; by OpenAI can experience this issue. ☠️&lt;/p&gt;

&lt;p&gt;While it's okay for creative prompts, like "&lt;em&gt;create a story about flying elephants playing soccer&lt;/em&gt;", we need often need reliable and accurate responses. That's where &lt;a href="https://microsoft.github.io/TypeChat/"&gt;Microsoft's TypeChat&lt;/a&gt; comes to the rescue with its schema engineering approach.&lt;/p&gt;

&lt;p&gt;Developers can define intent types using simple TypeScript. TypeChat then transforms prompts into JSON payloads matching with your business logic.&lt;/p&gt;

&lt;p&gt;With just 5 minutes of setup, you're good to go! Say goodbye to AI hallucinations and welcome reliable and accurate responses with TypeChat:&lt;/p&gt;

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

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>openai</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>From Bus Factor to Lottery Factor - Embracing Positive Framing in Team Dynamics</title>
      <dc:creator>Benny Code</dc:creator>
      <pubDate>Mon, 12 Jun 2023 15:47:59 +0000</pubDate>
      <link>https://forem.com/bennycode/from-bus-factor-to-lottery-factor-embracing-positive-framing-in-team-dynamics-4hhk</link>
      <guid>https://forem.com/bennycode/from-bus-factor-to-lottery-factor-embracing-positive-framing-in-team-dynamics-4hhk</guid>
      <description>&lt;p&gt;The &lt;strong&gt;bus factor&lt;/strong&gt; is a measure of a team's resilience and their ability to function effectively when key members are unavailable. It refers to the number of individuals who possess critical knowledge or expertise about a specific software or system. This concept highlights the risk or vulnerability that arises if those individuals were suddenly no longer accessible, such as due to unforeseen circumstances like being hit by a bus.&lt;/p&gt;

&lt;p&gt;Instead of focusing on the negative aspect of losing key team members, it's beneficial to shift our perspective to a more positive scenario. Consider a situation where team members win the lottery and choose to retire or pursue other interests. In this context, the &lt;strong&gt;lottery factor&lt;/strong&gt; represents the team's capability to smoothly carry on their work and maintain productivity despite the sudden departure of these fortunate individuals. &lt;/p&gt;

&lt;p&gt;While retirement typically involves a planned handover process, it also underscores the importance of &lt;strong&gt;knowledge sharing&lt;/strong&gt;, &lt;strong&gt;pair programming&lt;/strong&gt;, and having a &lt;strong&gt;well-documented system&lt;/strong&gt; to ensure the team's continued success, even in the face of unexpected changes or fortunate events.&lt;/p&gt;

&lt;p&gt;In general, adopting a &lt;strong&gt;positive framing&lt;/strong&gt; and replacing the notion of the "bus factor" with the "lottery factor" can help emphasize the team's ability to adapt and thrive under various circumstances. It encourages a focus on resilience while maintaining a positive ideology.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
      <category>leadership</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>What is control flow based narrowing in TypeScript?</title>
      <dc:creator>Benny Code</dc:creator>
      <pubDate>Mon, 05 Jun 2023 17:44:33 +0000</pubDate>
      <link>https://forem.com/typescripttv/what-is-control-flow-based-narrowing-in-typescript-55k2</link>
      <guid>https://forem.com/typescripttv/what-is-control-flow-based-narrowing-in-typescript-55k2</guid>
      <description>&lt;p&gt;&lt;strong&gt;Control flow based narrowing&lt;/strong&gt; refers to a type inference technique that allows the TypeScript compiler to narrow down the type of a variable based on the analysis of its control flow within conditional statements (such as &lt;code&gt;if&lt;/code&gt; statements and &lt;code&gt;switch&lt;/code&gt; statements).&lt;/p&gt;

&lt;p&gt;When TypeScript analyzes the control flow of a program, it can determine certain conditions that guarantee the state of a variable. This analysis helps TypeScript to infer a more specific type for that variable within the respective branch of the control flow.&lt;/p&gt;

&lt;p&gt;Consider the following TypeScript code snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// TypeScript knows here that `value` is of type `string`&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="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// TypeScript knows here that `value` is of type `number`&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="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&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;In this example, TypeScript uses &lt;strong&gt;control flow based narrowing&lt;/strong&gt; to determine that within the &lt;code&gt;if&lt;/code&gt; block, the variable &lt;code&gt;value&lt;/code&gt; is a &lt;code&gt;string&lt;/code&gt;. Conversely, in the &lt;code&gt;else&lt;/code&gt; block, it knows that &lt;code&gt;value&lt;/code&gt; is a &lt;code&gt;number&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;This enables the compiler to provide more accurate type checking and enables developers to use the appropriate properties or methods specific to each type without explicit type assertions.&lt;/p&gt;

&lt;p&gt;Control flow based narrowing is a powerful feature of TypeScript that enhances type safety and helps catch potential errors at compile-time by providing more precise type information based on the analysis of control flow within the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control Flow Based Type Analysis
&lt;/h2&gt;

&lt;p&gt;This video tutorial demonstrates control flow based narrowing:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Interested in learning more? 🎓
&lt;/h2&gt;

&lt;p&gt;I've spent years working with the TypeScript programming language and have encountered its various challenges. That's why I've recorded a series of &lt;a href="https://www.youtube.com/playlist?list=PLCbdBdyNHZXL3grXeUPZwTn1lo7pVaUdH"&gt;&lt;strong&gt;free TypeScript video tutorials&lt;/strong&gt;&lt;/a&gt;, designed to help you enhancing your coding skills. &lt;/p&gt;

&lt;p&gt;I invite you to take a look at them and provide your feedback on the videos. I'm eager to hear your thoughts and impressions. 👀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Why TypeScript is the better JavaScript</title>
      <dc:creator>Benny Code</dc:creator>
      <pubDate>Sun, 07 May 2023 18:45:07 +0000</pubDate>
      <link>https://forem.com/typescripttv/why-typescript-is-the-better-javascript-f5p</link>
      <guid>https://forem.com/typescripttv/why-typescript-is-the-better-javascript-f5p</guid>
      <description>&lt;p&gt;JavaScript is a powerful and widely used programming language for web applications. However, it can sometimes lead to unexpected results and errors due to its dynamic type system. In this blog post, we'll examine some of the most common errors that arise in JavaScript, and explore how &lt;a href="https://www.typescriptlang.org/"&gt;TypeScript&lt;/a&gt;, a superset of JavaScript, can help prevent these issues with improved type safety and error handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common JavaScript Errors
&lt;/h2&gt;

&lt;p&gt;JavaScript is a beginner-friendly language due to its minimal setup requirements. However, the language's limited syntax and dynamic typing often lead to errors, making it challenging for even those who are familiar with the language.&lt;/p&gt;

&lt;p&gt;Here are a few common errors when working with JavaScript:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unexpected string concatenation:&lt;/strong&gt; Mixing numbers and strings can lead to undesired results, such as &lt;code&gt;console.log(10 + '100')&lt;/code&gt; outputting &lt;code&gt;10100&lt;/code&gt; instead of the expected &lt;code&gt;110&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stringified object concatenation:&lt;/strong&gt; Using an object as an argument will result in a stringified version of the object combined with the other argument, e.g., &lt;code&gt;console.log({} + '10')&lt;/code&gt; returns &lt;code&gt;[object Object]10&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeError:&lt;/strong&gt; Accessing properties of an object that are undefined or calling a non-function will trigger a &lt;code&gt;TypeError&lt;/code&gt;, such as &lt;code&gt;caught TypeError: Cannot read properties of undefined (reading 'data')&lt;/code&gt; when calling &lt;code&gt;console.log({}.input.data)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NaN:&lt;/strong&gt; Forgetting to pass an argument results in a non-existent number, commonly referred to as &lt;code&gt;NaN&lt;/code&gt; for "Not a Number". Example: &lt;code&gt;console.log(parseInt())&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ReferenceError:&lt;/strong&gt; Using an undeclared variable results in a &lt;code&gt;ReferenceError&lt;/code&gt;, such as &lt;code&gt;console.log(10 + abc)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not defined functions:&lt;/strong&gt; Calling functions that don't exist will also throw a &lt;code&gt;ReferenceError&lt;/code&gt;. Example: &lt;code&gt;console.log(abc())&lt;/code&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript to the Rescue
&lt;/h2&gt;

&lt;p&gt;TypeScript is a programming language developed and maintained by Microsoft that adds type safety to JavaScript. By using TypeScript, you can catch errors during design time (when writing code) rather than at runtime. This will help you catching common JavaScript errors before they appear in production. TypeScript code is compiled (or transpiled) to JavaScript, ensuring compatibility with web applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use TypeScript?
&lt;/h2&gt;

&lt;p&gt;TypeScript offers several benefits, including:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Strong type checking to catch errors before runtime&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.visualstudio.com/docs/editor/intellisense"&gt;Code autocompletion&lt;/a&gt; in your IDE for a smoother coding experience&lt;/li&gt;
&lt;li&gt;Fundamental linting rules, including detection of unused variables and parameters&lt;/li&gt;
&lt;li&gt;Const assertions to avoid side effects in functional programming&lt;/li&gt;
&lt;li&gt;Class decorators to facilitate dependency injection&lt;/li&gt;
&lt;li&gt;Inherent support for &lt;a href="https://facebook.github.io/jsx/"&gt;JSX&lt;/a&gt; in the language itself&lt;/li&gt;
&lt;li&gt;Downleveling to convert modern JavaScript to earlier versions of JavaScript&lt;/li&gt;
&lt;li&gt;Compatibility with various module systems, such as &lt;a href="https://nodejs.org/api/modules.html#modules-commonjs-modules"&gt;CommonJS&lt;/a&gt; and &lt;a href="https://nodejs.org/api/esm.html#modules-ecmascript-modules"&gt;ECMAScript modules&lt;/a&gt; (ESM)&lt;/li&gt;
&lt;li&gt;Customizable output formatting options, including end-of-line sequence formatting&lt;/li&gt;
&lt;li&gt;Support for polymorphism via class inheritance and interfaces in object-oriented programming&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting Started with TypeScript
&lt;/h2&gt;

&lt;p&gt;As an experienced TypeScript contributor, I've spent years working with the language and have encountered its various challenges. That's why I've created a &lt;a href="https://www.youtube.com/playlist?list=PLCbdBdyNHZXL3grXeUPZwTn1lo7pVaUdH"&gt;&lt;strong&gt;free TypeScript video tutorials&lt;/strong&gt;&lt;/a&gt;, designed to help you master the latest version and enhance your coding skills. This series focuses on the fundamentals of TypeScript, ensuring you not only grasp how things work, but also why they work the way they do.&lt;/p&gt;

&lt;p&gt;This knowledge can be a valuable asset as you progress from a junior to a senior programmer, and can even assist you in tackling difficult questions during job interviews. The best part? This entire tutorial series is &lt;strong&gt;available at no cost&lt;/strong&gt;, so tune in and enjoy your journey towards TypeScript mastery.&lt;/p&gt;

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

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Understanding npm Versioning</title>
      <dc:creator>Benny Code</dc:creator>
      <pubDate>Tue, 04 Apr 2023 15:00:00 +0000</pubDate>
      <link>https://forem.com/typescripttv/understanding-npm-versioning-3hn4</link>
      <guid>https://forem.com/typescripttv/understanding-npm-versioning-3hn4</guid>
      <description>&lt;p&gt;In the world of software development with JavaScript and TypeScript, it is crucial to manage the &lt;strong&gt;versions of packages&lt;/strong&gt; used in your projects. This blog post will show the details of npm package management, specifically focusing on the &lt;strong&gt;caret&lt;/strong&gt; (&lt;code&gt;^&lt;/code&gt;) and &lt;strong&gt;tilde&lt;/strong&gt; (&lt;code&gt;~&lt;/code&gt;) symbols, as well as other versioning notations. Get ready to better understand how to manage your project's dependencies and establish reproducible builds with consistency in package versions!&lt;/p&gt;

&lt;h2&gt;
  
  
  Major, minor, patch
&lt;/h2&gt;

&lt;p&gt;The npm ecosystem uses &lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;semantic versioning&lt;/a&gt; where version numbers typically consist of three parts, separated by dots: &lt;code&gt;major.minor.patch&lt;/code&gt;. These parts represent different types of changes made to the software, and are used to help developers and users understand the significance of a given version number.&lt;/p&gt;

&lt;h3&gt;
  
  
  Major version
&lt;/h3&gt;

&lt;p&gt;The major version number is typically incremented when there are breaking changes to the software. It indicates that the software is no longer backwards compatible with previous versions, and users may need to make changes to their code in order to upgrade. &lt;/p&gt;

&lt;p&gt;For instance, API contracts may change if parameters are modified:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version 1.0.0&lt;/strong&gt;&lt;/p&gt;

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

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Version 2.0.0&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The API contract for Version 1.0.0 has been violated due to changes in the input parameter types for &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;. As a result, Version 2.0.0 must be introduced to address this breaking change:&lt;/p&gt;

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

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Following the &lt;a href="https://docs.npmjs.com/cli/v6/using-npm/semver#caret-ranges-123-025-004" rel="noopener noreferrer"&gt;npm guidelines&lt;/a&gt;, you can make breaking changes in your APIs as long as you are below version 1.0.0. When your package is in major version zero (e.g., &lt;code&gt;0.x.x&lt;/code&gt;), the semver rules change slightly. In this case, breaking changes occur in &lt;code&gt;0.x.x&lt;/code&gt; (minor-level), while new features and patches are implemented in &lt;code&gt;0.0.x&lt;/code&gt; (patch-level).&lt;/p&gt;

&lt;h3&gt;
  
  
  Minor version
&lt;/h3&gt;

&lt;p&gt;The minor version number is typically incremented when new features are added to the software, or when there are significant enhancements or improvements to existing features. These changes are usually backwards compatible, meaning that users can upgrade to the new version without having to make major changes to their code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version 1.0.0&lt;/strong&gt;&lt;/p&gt;

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

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Version 1.1.0&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;add&lt;/code&gt; function has been updated to accept an indefinite amount of numbers, improving on its original implementation. This update does not affect prior API calls, as the function can still be used by providing just two numeric arguments, maintaining backwards compatibility.&lt;/p&gt;

&lt;p&gt;In addition to the existing &lt;code&gt;add&lt;/code&gt; function, a new &lt;code&gt;subtract&lt;/code&gt; function has been added. It requires a minor release (&lt;code&gt;1.1.0&lt;/code&gt;) to introduce these new features:&lt;/p&gt;

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

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;current&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;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Patch version
&lt;/h3&gt;

&lt;p&gt;The patch version number is typically incremented when bugs or security issues are fixed in the software. These changes are generally small, and do not involve major changes to the functionality or features of the software. &lt;/p&gt;

&lt;p&gt;Let's assume we updated the &lt;code&gt;subtract&lt;/code&gt; function from version 1.1.0 to 1.2.0, adding the ability to also accept an indefinite amount of numbers as input:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version 1.2.0&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;current&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;sum&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Suppose we've discovered that using &lt;code&gt;0&lt;/code&gt; as the initial value for the subtract function was a mistake. Rather than subtracting from &lt;code&gt;0&lt;/code&gt;, we want the function to subtract from the first number that is given as input. As a result, we'll need to release a patch version (&lt;code&gt;1.2.1&lt;/code&gt;) to fix this issue:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version 1.2.1&lt;/strong&gt;&lt;/p&gt;


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

&lt;p&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;current&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;sum&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

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

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Understanding Caret (&lt;code&gt;^&lt;/code&gt;) and Tilde (&lt;code&gt;~&lt;/code&gt;)&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;When specifying dependencies in our &lt;code&gt;package.json&lt;/code&gt; file, the caret and tilde symbols have special significance in determining the range of acceptable package versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Video Tutorial
&lt;/h3&gt;

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

&lt;h3&gt;
  
  
  Caret (&lt;code&gt;^&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The caret symbol indicates that npm should restrict upgrades to &lt;strong&gt;patch or minor level updates&lt;/strong&gt;, without allowing major version updates. For example, given &lt;code&gt;"^5.0.2"&lt;/code&gt;, npm will permit updates within the same major version (e.g., &lt;code&gt;5.1.0&lt;/code&gt;, &lt;code&gt;5.0.3&lt;/code&gt;), but not a jump to a new major version (e.g., &lt;code&gt;6.0.0&lt;/code&gt;) when running &lt;code&gt;npm update&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tilde (&lt;code&gt;~&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;By altering the caret symbol to a tilde symbol, we would only receive updates at the &lt;strong&gt;patch level&lt;/strong&gt;. If we were to use &lt;code&gt;"~5.0.2"&lt;/code&gt;, we would obtain version &lt;code&gt;5.0.3&lt;/code&gt; if it were available, but not &lt;code&gt;5.1.0&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trick to remember
&lt;/h3&gt;

&lt;p&gt;A helpful way to remember the difference between these symbols is by envisioning a house: the tilde is the floor, while the caret represents the rooftop, enabling you to reach higher version numbers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4uequ96jaff7cm5a4s2.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%2Fj4uequ96jaff7cm5a4s2.PNG" alt="Caret and Tilde"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternative Versioning Notations
&lt;/h2&gt;

&lt;p&gt;If you're not concerned with specifying the precise minimum version and simply want to obtain the most recent version in a certain range, you can use alternative notations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Latest Patch Version
&lt;/h3&gt;

&lt;p&gt;To get the latest &lt;strong&gt;patch version&lt;/strong&gt;, use the notation &lt;code&gt;5.0.x&lt;/code&gt;. This will install the most recent version with the given major and minor numbers (e.g., &lt;code&gt;5.0.6&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Latest Minor and Patch Versions
&lt;/h3&gt;

&lt;p&gt;Similarly, to obtain the latest &lt;strong&gt;minor version&lt;/strong&gt;, along with its most &lt;strong&gt;recent patch&lt;/strong&gt;, use the notation &lt;code&gt;5.x.x&lt;/code&gt;. This will permit updates within the same major version, but without restrictions on the minor version (e.g., &lt;code&gt;5.1.3&lt;/code&gt; or &lt;code&gt;5.2.0&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating Dependencies
&lt;/h2&gt;

&lt;p&gt;It's essential to properly manage your dependency updates when changing version numbers in your &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;

&lt;h3&gt;
  
  
  npm install
&lt;/h3&gt;

&lt;p&gt;Executing the &lt;code&gt;npm install&lt;/code&gt; command installs a package's dependencies and any dependencies that these dependencies rely on (transitive dependencies). It also creates a &lt;code&gt;package-lock.json&lt;/code&gt; file if it doesn't exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; It is worth mentioning that &lt;code&gt;npm install&lt;/code&gt; will download &lt;em&gt;compatible versions&lt;/em&gt; when there is no &lt;code&gt;package-lock.json&lt;/code&gt; file present. This means that the version you receive may not be the latest minor version, even if you've specified a version range using the caret symbol.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you've included &lt;code&gt;typescript@^4.8.3&lt;/code&gt; in the &lt;code&gt;devDependencies&lt;/code&gt; section of your &lt;code&gt;package.json&lt;/code&gt; file, you may expect to receive the latest minor version (currently &lt;code&gt;4.9.5&lt;/code&gt;) when you run &lt;code&gt;npm install&lt;/code&gt;. However, you will receive the latest patch version (&lt;code&gt;4.8.4&lt;/code&gt;) as it is compatible with your version range. In order to obtain &lt;code&gt;4.9.5&lt;/code&gt;, you will need to execute &lt;code&gt;npm update&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dependency&lt;/th&gt;
&lt;th&gt;npm install&lt;/th&gt;
&lt;th&gt;npm update&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;typescript@~4.8.3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4.8.4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4.8.4&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;typescript@^4.8.3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4.8.4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4.9.5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;typescript@4.8.X&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4.8.4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4.8.4&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;typescript@4.X.X&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4.9.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4.9.5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  npm update
&lt;/h3&gt;

&lt;p&gt;To apply changes to the version numbers in your &lt;code&gt;package.json&lt;/code&gt;, use the &lt;code&gt;npm update&lt;/code&gt; command. Unlike &lt;code&gt;npm install&lt;/code&gt;, which only downloads a compatible version, &lt;code&gt;npm update&lt;/code&gt; will also update the package to the latest version that matches the specified range. Additionally, the &lt;code&gt;package-lock.json&lt;/code&gt; file will be updated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recording Exact Versions
&lt;/h3&gt;

&lt;p&gt;You might wonder: "How can I determine which version was actually downloaded?" That's where the &lt;code&gt;package-lock.json&lt;/code&gt; file comes in. This file records the exact version retrieved during the initial installation, or any subsequent updates.&lt;/p&gt;

&lt;p&gt;With a &lt;code&gt;package-lock.json&lt;/code&gt; file in place, future runs of &lt;code&gt;npm install&lt;/code&gt; will download the same build, ensuring &lt;strong&gt;reproducible results&lt;/strong&gt; for your project's dependencies. This is particularly important in collaborative development environments, where multiple team members work on the same project, as it guarantees consistency across all installations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traffic Light Control
&lt;/h3&gt;

&lt;p&gt;If you use the &lt;a href="https://yarnpkg.com/" rel="noopener noreferrer"&gt;Yarn Package Manager&lt;/a&gt; instead of &lt;a href="https://www.npmjs.com/" rel="noopener noreferrer"&gt;npm&lt;/a&gt; and run the command &lt;code&gt;yarn upgrade-interactive --latest&lt;/code&gt;, you'll see a color-coded legend that indicates the likelihood of a package update breaking your code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Patch updates&lt;/strong&gt; are indicated in &lt;strong&gt;green&lt;/strong&gt;, as they typically include backward-compatible fixes and pose the least risk of breaking your application&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minor updates&lt;/strong&gt; are indicated in &lt;strong&gt;yellow&lt;/strong&gt;, as they typically involve more changes than patch updates and therefore carry a higher risk of causing issues if their package maintainers don't strictly adhere to semantic versioning guidelines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Major updates&lt;/strong&gt; are indicated in &lt;strong&gt;red&lt;/strong&gt;, as they often include backward-incompatible changes that may require you to update your code in order to maintain compatibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Screenshot&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnup5tjvx86sg54rpe1o4.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%2Fnup5tjvx86sg54rpe1o4.png" alt="yarn upgrade-interactive"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conventional Commits
&lt;/h2&gt;

&lt;p&gt;As we've learned, major version updates should only be issued when there are breaking changes. To draw attention to a breaking change, the &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/#commit-message-with--to-draw-attention-to-breaking-change" rel="noopener noreferrer"&gt;Conventional Commits specification&lt;/a&gt; suggests using an exclamation mark (&lt;code&gt;!&lt;/code&gt;) after the &lt;strong&gt;type&lt;/strong&gt; or &lt;strong&gt;scope&lt;/strong&gt; in a commit message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;refactor(api)!: use stringified numbers in calculations&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tools for publishing, such as &lt;a href="https://lerna.js.org/" rel="noopener noreferrer"&gt;Lerna&lt;/a&gt; (when using the &lt;code&gt;--conventional-commit&lt;/code&gt; flag), follow this convention when incrementing package versions and generating changelog files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing packages
&lt;/h2&gt;

&lt;p&gt;Publishing packages with npm is a crucial step in sharing your code with the wider community. The npm CLI provides commands (&lt;code&gt;npm version&lt;/code&gt; and &lt;code&gt;npm publish&lt;/code&gt;) to assist with proper versioning and release of your own packages, in accordance with semantic versioning rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Video&lt;/strong&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  npm version
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.npmjs.com/cli/v9/commands/npm-version" rel="noopener noreferrer"&gt;npm version&lt;/a&gt; command is used to update the version of a package in the &lt;code&gt;package.json&lt;/code&gt; file. If run in a Git repository, it will generate a Git tag and a new commit with a message that includes the version number.&lt;/p&gt;

&lt;h3&gt;
  
  
  npm publish
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.npmjs.com/cli/v9/commands/npm-publish" rel="noopener noreferrer"&gt;npm publish&lt;/a&gt; command is used to publish the package to the registry, making it available for installation by others.&lt;/p&gt;

&lt;h3&gt;
  
  
  Release Workflow
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;npm version&lt;/code&gt; and &lt;code&gt;npm publish&lt;/code&gt; commands can be used to set up release workflows within the scripts section of a &lt;code&gt;package.json&lt;/code&gt; file:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;br&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;br&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"preversion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git checkout main &amp;amp;&amp;amp; git pull &amp;amp;&amp;amp; npm install &amp;amp;&amp;amp; npm test &amp;amp;&amp;amp; npm run build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"release:major"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm version major"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"release:minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm version minor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"release:patch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm version patch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"postversion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git push origin &amp;amp;&amp;amp; git push origin --tags &amp;amp;&amp;amp; npm publish --access public"&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Conclusion&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;In this blog post, we've explored the significance of the caret and tilde symbols in npm versioning, as well as alternative versioning notations. We've also looked at how to use the &lt;code&gt;npm update&lt;/code&gt; command and the importance of the &lt;code&gt;package-lock.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;With a better understanding of npm package management and the tools available to manage dependency versions, you can ensure a stable, consistent, and reproducible development environment for your projects. Happy coding!&lt;/p&gt;

</description>
      <category>npm</category>
      <category>node</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>What is the difference between null and undefined?</title>
      <dc:creator>Benny Code</dc:creator>
      <pubDate>Sun, 12 Mar 2023 17:28:15 +0000</pubDate>
      <link>https://forem.com/typescripttv/what-is-the-difference-between-null-and-undefined-5h76</link>
      <guid>https://forem.com/typescripttv/what-is-the-difference-between-null-and-undefined-5h76</guid>
      <description>&lt;p&gt;The convention in TypeScript is that &lt;code&gt;undefined&lt;/code&gt; values have not been defined yet, whereas &lt;code&gt;null&lt;/code&gt; values indicate intentional absence of a value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example with &lt;code&gt;null&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The below function shows how &lt;code&gt;null&lt;/code&gt; can be used by returning an object that always has the same structure, but with &lt;strong&gt;intentionally assigned&lt;/strong&gt; &lt;code&gt;null&lt;/code&gt; values when the function does not return an &lt;code&gt;error&lt;/code&gt; or &lt;code&gt;result&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;b&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Division by zero&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;  
    &lt;span class="p"&gt;};&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;  
    &lt;span class="p"&gt;};&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example with &lt;code&gt;undefined&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;On the other hand, &lt;code&gt;undefined&lt;/code&gt; represents the absence of any value. It is a value that is &lt;strong&gt;automatically assigned&lt;/strong&gt; to a variable when no other value is assigned. It often indicates that a variable has been declared but not initialized. It can also signify a programming mistake, such as when a property or function parameter was not provided:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ratio&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="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;Someone forgot to assign a value.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ratio&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="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;Someone chose not to assign a value.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Best Practice
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;TypeScript Coding guidelines&lt;/strong&gt; recommend using only &lt;code&gt;undefined&lt;/code&gt; and discouraging the use of &lt;code&gt;null&lt;/code&gt; values (see &lt;a href="https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines#null-and-undefined"&gt;here&lt;/a&gt;). It is important to note, however, that these guidelines are tailored towards the TypeScript project's codebase and may not necessarily be applicable to your own projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want more?
&lt;/h2&gt;

&lt;p&gt;If you found this short explainer helpful, hit that &lt;strong&gt;subscribe button&lt;/strong&gt; on my &lt;a href="https://www.youtube.com/typescripttv?sub_confirmation=1"&gt;YouTube channel&lt;/a&gt; or give me a &lt;a href="https://twitter.com/bennycode"&gt;follow on Twitter&lt;/a&gt; to level up your TypeScript game.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Best practices with const assertions in TypeScript</title>
      <dc:creator>Benny Code</dc:creator>
      <pubDate>Tue, 03 Jan 2023 18:38:14 +0000</pubDate>
      <link>https://forem.com/typescripttv/best-practices-with-const-assertions-in-typescript-4l10</link>
      <guid>https://forem.com/typescripttv/best-practices-with-const-assertions-in-typescript-4l10</guid>
      <description>&lt;p&gt;TypeScript 3.4 introduced &lt;strong&gt;const assertions&lt;/strong&gt;, a feature that allows you to claim a value as immutable. This feature is particularly valuable in combination with array literals, as it prevents new values from being pushed into an existing array.&lt;/p&gt;

&lt;h2&gt;
  
  
  const declarations
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;const declaration&lt;/strong&gt; only makes the &lt;strong&gt;array reference&lt;/strong&gt; immutable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&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="c1"&gt;// 'const' only makes the array reference immutable&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myArray&lt;/span&gt; &lt;span class="o"&gt;=&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;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// Values of the array can still be removed&lt;/span&gt;
&lt;span class="nx"&gt;myArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// ...or pushed&lt;/span&gt;
&lt;span class="nx"&gt;myArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ... but reassignments become impossible&lt;/span&gt;
&lt;span class="c1"&gt;// TS2588: Cannot assign to 'myArray' because it is a constant.&lt;/span&gt;
&lt;span class="nx"&gt;myArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  const assertions
&lt;/h2&gt;

&lt;p&gt;With a &lt;strong&gt;const assertion&lt;/strong&gt; you will get immutability for your &lt;strong&gt;array reference&lt;/strong&gt; and &lt;strong&gt;array values&lt;/strong&gt; at design-time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&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="c1"&gt;// A 'const assertion' protects the reference and elements of your array&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myArray&lt;/span&gt; &lt;span class="o"&gt;=&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;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// TS2339: Property 'pop' does not exist on type 'readonly [1, 2, 3]'.&lt;/span&gt;
&lt;span class="nx"&gt;myArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// TS2339: Property 'push' does not exist on type 'readonly [1, 2, 3]'.&lt;/span&gt;
&lt;span class="nx"&gt;myArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// TS2588: Cannot assign to 'myArray' because it is a constant.&lt;/span&gt;
&lt;span class="nx"&gt;myArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Important note
&lt;/h2&gt;

&lt;p&gt;One important thing to note is that const assertions are a TypeScript feature at &lt;strong&gt;design-time&lt;/strong&gt; and do not actually make values immutable at runtime. This means that you will still be able to modify values at runtime using plain JavaScript. If you want to achieve a higher level of immutability, you can make use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze" rel="noopener noreferrer"&gt;Object.freeze() API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myArray&lt;/span&gt; &lt;span class="o"&gt;=&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;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// The static 'freeze' method makes your array immutable at runtime&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myArray&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// This will happen when you try to manipulate your array in JavaScript:&lt;/span&gt;
&lt;span class="c1"&gt;// Uncaught TypeError: Cannot add property 4, object is not extensible&lt;/span&gt;
&lt;span class="nx"&gt;myArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Best Practice
&lt;/h2&gt;

&lt;p&gt;You can combine a &lt;strong&gt;const assertion&lt;/strong&gt; with &lt;code&gt;Object.freeze&lt;/code&gt; to get type safety at &lt;strong&gt;design-time&lt;/strong&gt; and &lt;strong&gt;runtime&lt;/strong&gt;:&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="c1"&gt;// Protection at design-time&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myArray&lt;/span&gt; &lt;span class="o"&gt;=&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;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Protection at runtime&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myArray&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  as const with objects
&lt;/h2&gt;

&lt;p&gt;A const assertion can also protect an object literal by marking its properties as &lt;code&gt;readonly&lt;/code&gt;:&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="c1"&gt;// Protect your object properties from changes at design-time&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;objectLiteral&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Benny&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// TS2540: Cannot assign to 'age' because it is a read-only property.&lt;/span&gt;
&lt;span class="nx"&gt;objectLiteral&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  as const with strings
&lt;/h2&gt;

&lt;p&gt;You can narrow the type for a string to a specific string using &lt;code&gt;as const&lt;/code&gt;:&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="c1"&gt;// Don't use an explicit return type to make use of type inference&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;firstName&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;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Benny&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;Code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// TS2322: Type '"..."' is not assignable to type '`${string} ${string}`'.&lt;/span&gt;
&lt;span class="nx"&gt;displayName&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Alternative Options
&lt;/h2&gt;

&lt;p&gt;You can use the &lt;code&gt;ReadonlyArray&lt;/code&gt; type of TypeScript 3.4 to disallow array value modifications at design-time:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReadonlyArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&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;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// TS2339: Property 'pop' does not exist on type 'readonly number[]'.&lt;/span&gt;
&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Shorthand Syntax&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// TS2339: Property 'pop' does not exist on type 'readonly number[]'.&lt;/span&gt;
&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

</description>
      <category>diversity</category>
      <category>inclusion</category>
      <category>discuss</category>
      <category>php</category>
    </item>
    <item>
      <title>Record in 4K with Sony Alpha A6400 &amp; Elgato Cam Link 4K</title>
      <dc:creator>Benny Code</dc:creator>
      <pubDate>Sat, 14 May 2022 15:05:33 +0000</pubDate>
      <link>https://forem.com/bennycode/record-in-4k-with-sony-alpha-a6400-elgato-cam-link-4k-1l6g</link>
      <guid>https://forem.com/bennycode/record-in-4k-with-sony-alpha-a6400-elgato-cam-link-4k-1l6g</guid>
      <description>&lt;p&gt;Most of my programming tutorials, like &lt;a href="https://www.youtube.com/watch?v=gURahFPPos0" rel="noopener noreferrer"&gt;5 tips to write better TypeScript code&lt;/a&gt;, have an intro in 4K resolution. I record my intros using a Sony Alpha 6400 mirrorless camera. I get the video signal from my Sony A6400 through my PC, but it wasn't that easy to capture it in 4K with 30 frames per second (2160p30). In this post, I will show you how I managed to do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sony Imaging Edge
&lt;/h2&gt;

&lt;p&gt;First I tried using Sony's &lt;a href="https://support.d-imaging.sony.co.jp/app/webcam/en/" rel="noopener noreferrer"&gt;Imaging Edge Webcam&lt;/a&gt; software as I was hoping I could simply use a USB cable and get the video signal. It actually worked but the maximum resolution was limited to 1024×576 pixels. Higher resolutions are not available using the the USB output of the Sony Alpha 6400. Sony's &lt;a href="https://support.d-imaging.sony.co.jp/app/webcam/en/instruction/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; confirms this limitation and also mentions that the &lt;strong&gt;Imaging Edge Webcam&lt;/strong&gt; software does not handle audio from the camera's built-in microphone or a connected microphone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Elgato Cam Link 4K
&lt;/h2&gt;

&lt;p&gt;Since 4K recording via Micro USB isn't possible with the A6400, I switched to recording via Micro HDMI in combination with a so called video capture card. I bought the &lt;a href="https://www.elgato.com/en/cam-link-4k" rel="noopener noreferrer"&gt;Elgato Cam Link 4K&lt;/a&gt; as it promises out of the box recording with up to 4K resolution at 30 frames per second.&lt;/p&gt;

&lt;h3&gt;
  
  
  USB Cable
&lt;/h3&gt;

&lt;p&gt;To connect my camera with the Cam Link 4K, I bought a &lt;strong&gt;Micro HDMI to HDMI cable&lt;/strong&gt; of 3m length that has been certified for 4K at 60Hz. The length of the cable plays an important role. Long HDMI cables can cause a video signal degradation. Best is to check with the HDMI cable manufacturer for more details.&lt;/p&gt;

&lt;h3&gt;
  
  
  USB Port
&lt;/h3&gt;

&lt;p&gt;I had to make sure that the Capture Card is connected to its own USB port on my mainboard, so that it doesn't share the USB controller with any other device. I connected it to a USB 3.1 port but the &lt;a href="https://help.elgato.com/hc/en-us/articles/360041899471-Cam-Link-4K-Troubleshooting-Frozen-Video#USB_Port" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; says that a &lt;strong&gt;USB 3.0 port&lt;/strong&gt; is sufficient. When I had the Capture Card connected to a USB Hub, then it didn't work as it shared the bandwidth with other devices (such as my webcam) and as a result didn't get enough bandwidth to transfer the video signal without interruptions. I had to balance the load on my USB controllers.&lt;/p&gt;

&lt;h3&gt;
  
  
  USB Controller
&lt;/h3&gt;

&lt;p&gt;Here is how you can verify if your Cam Link 4K is connected to its &lt;strong&gt;own USB controller&lt;/strong&gt; on Windows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open Windows' Device Manager&lt;/li&gt;
&lt;li&gt;Select "View" and "Devices by type"&lt;/li&gt;
&lt;li&gt;Select "Cameras" and "Cam Link 4K"&lt;/li&gt;
&lt;li&gt;Change "View" to "Devices by connection"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It should show you now the USB controller to which your Cam Link 4K is attached. Make sure your capture card doesn't share the USB controller with another device:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4rgasi3xmzf1umjifhq.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%2Fy4rgasi3xmzf1umjifhq.png" alt="Windows Device Manager"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;For macOS you can &lt;a href="https://help.elgato.com/hc/en-us/articles/360028238651-Two-Cam-Link-4K-units-on-one-Computer-Two-USB-3-0-Root-Hubs-Needed" rel="noopener noreferrer"&gt;find out here&lt;/a&gt; how many USB Root Hubs are available on your machine and what is connected to them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Software
&lt;/h3&gt;

&lt;p&gt;Elgato advertises Plug'n'Play installation which means that you can simply connect the Cam Link 4K USB stick to your computer and use it directly in applications without installing any additional software. While that is true, I recommend you to install the &lt;a href="https://help.elgato.com/hc/en-us/articles/360027964472-Elgato-4K-Capture-Utility-Release-Notes-#1.7.6" rel="noopener noreferrer"&gt;Elgato 4K Capture Utility 1.7.6&lt;/a&gt; for troubleshooting. There is also the &lt;a href="https://help.elgato.com/hc/en-us/sections/4880787756941-Elgato-Camera-Hub-Release-Notes" rel="noopener noreferrer"&gt;Elgato Camera Hub&lt;/a&gt; but it won't give you as much recording information as the Capture Utility.&lt;/p&gt;

&lt;p&gt;The first thing I noticed in the 4K Capture Utility was that my camera was only sending 25 (not 30!) frames per second. That was mainly because it was set to PAL instead of NTSC.&lt;/p&gt;

&lt;p&gt;In the screenshot you can see that there is a problem with the recording setting:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jehjx2y09fn7llkp415.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%2F1jehjx2y09fn7llkp415.png" alt="Output problem"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Video Settings
&lt;/h2&gt;

&lt;p&gt;Here is how I fixed my recording settings to make the Sony A6400 (ILCE-6400) capturing 4K video with 30fps. &lt;/p&gt;

&lt;p&gt;At first you have to turn the &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002278968.html" rel="noopener noreferrer"&gt;mode dial&lt;/a&gt; to the "&lt;strong&gt;Movie&lt;/strong&gt;" icon. Afterwards you have to go through the following settings using the "&lt;strong&gt;MENU&lt;/strong&gt;" button:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setup → Setup2 → &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002241763.html" rel="noopener noreferrer"&gt;NTSC/PAL Selector&lt;/a&gt; → Change to &lt;strong&gt;NTSC&lt;/strong&gt; and reboot your camera&lt;/li&gt;
&lt;li&gt;Setup → Setup4 → HDMI Settings → &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002241314.html" rel="noopener noreferrer"&gt;HDMI Resolution&lt;/a&gt; → Select &lt;strong&gt;2160p/1080p&lt;/strong&gt; to stream 4K video via HDMI&lt;/li&gt;
&lt;li&gt;Setup → Setup4 → HDMI Settings → &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002241318.html" rel="noopener noreferrer"&gt;HDMI Info. Display&lt;/a&gt; → Select &lt;strong&gt;Off&lt;/strong&gt; to disable menu icons in your video stream (Clean HDMI Output)&lt;/li&gt;
&lt;li&gt;Camera Settings2 → Movie1 → &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002264695.html" rel="noopener noreferrer"&gt;File Format&lt;/a&gt; → Select &lt;strong&gt;XAVC S 4K&lt;/strong&gt; to record movies in 4K resolution (3840×2160)&lt;/li&gt;
&lt;li&gt;Camera Settings2 → Movie1 → &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002264696.html?search=30p%2060M" rel="noopener noreferrer"&gt;Record Setting&lt;/a&gt; → &lt;strong&gt;30p 60M&lt;/strong&gt; to record with 30 frames per second and a bitrate of approx. 60 Mbps. You can also use &lt;strong&gt;30p 100M&lt;/strong&gt; if your computer handles the data well during post-processing and you have enough disk storage for 100 Mbps. However, the 4K Capture Utility only supports 70 Mbps and the &lt;a href="https://www.youtube.com/watch?v=7fb7hHBaPto" rel="noopener noreferrer"&gt;difference between 60 Mbps &amp;amp; 100 Mbps is marginal&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Connect HDMI cable to your camera to see the &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002273469.html" rel="noopener noreferrer"&gt;4K Output Select&lt;/a&gt; option&lt;/li&gt;
&lt;li&gt;Setup → Setup4 → &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002273469.html" rel="noopener noreferrer"&gt;4K Output Select&lt;/a&gt; → &lt;strong&gt;HDMI Only (30p)&lt;/strong&gt; to stream 30 frames per second via HDMI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is how it should look in the "4K Capture Utility" when all settings are correct:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fklm47zvcv0prwsrklimh.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%2Fklm47zvcv0prwsrklimh.png" alt="2160p30 at 60 Mbps"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You don't have to insert a SD card or press the record button on your Sony camera as the video signal will be streamed through the High Definition Multimedia Interface (HDMI). Recording happens on your computer. If you want to record over a longer period of time without draining your battery, then I recommend to buy the &lt;strong&gt;Sony NP-FW 50&lt;/strong&gt; power adapter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exposure Settings
&lt;/h2&gt;

&lt;p&gt;Further settings that I use when recoding my intros:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Camera Settings1 → AF1 → &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002273481.html" rel="noopener noreferrer"&gt;Focus Mode&lt;/a&gt; → &lt;strong&gt;Continuous AF&lt;/strong&gt; to make sure that the focus gets automatically adjusted when I move in front of my camera&lt;/li&gt;
&lt;li&gt;Camera Settings1 → AF1 → &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002264665.html" rel="noopener noreferrer"&gt;Focus Area&lt;/a&gt; → &lt;strong&gt;Wide&lt;/strong&gt; to focus my whole scene / room&lt;/li&gt;
&lt;li&gt;Camera Settings1 → Colors → &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002264690.html" rel="noopener noreferrer"&gt;White Balance&lt;/a&gt; → &lt;strong&gt;Auto&lt;/strong&gt; so that I can record at different times with different lighting conditions without manually adjusting my white balance setting (WBS) &lt;/li&gt;
&lt;li&gt;Camera Settings1 → Exposure1 → ISO Setting → &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002264686.html" rel="noopener noreferrer"&gt;ISO&lt;/a&gt; → &lt;strong&gt;ISO 100&lt;/strong&gt; or in general the lowest value possible that doesn't make my scene look too dark&lt;/li&gt;
&lt;li&gt;Camera Settings1 → Exposure1 → &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002241212.html" rel="noopener noreferrer"&gt;Metering Mode&lt;/a&gt; → &lt;strong&gt;Multi&lt;/strong&gt; to determine the proper exposure for your entire scene&lt;/li&gt;
&lt;li&gt;Camera Settings1 → Exposure1 → &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002279219.html" rel="noopener noreferrer"&gt;Face Priority in Multi Metering&lt;/a&gt; → &lt;strong&gt;On&lt;/strong&gt; to make sure that your face always gets exposed in your scene&lt;/li&gt;
&lt;li&gt;Camera Settings2 → Movie1 → &lt;a href="https://dev.toExposure%20Mode"&gt;Exposure Mode&lt;/a&gt; → &lt;strong&gt;Manual Exposure&lt;/strong&gt; to control your aperture and shutter-speed manually&lt;/li&gt;
&lt;li&gt;Make sure your shutter speed is set to 1/60 to adhere to the "180 Degree Rule of Shutter Speed" (shutter speed should be set to 1/frame rate x 2) when recording with 30fps&lt;/li&gt;
&lt;li&gt;Movie → Movie2 → &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002273505.html" rel="noopener noreferrer"&gt;AF Tracking Sens.&lt;/a&gt; → &lt;strong&gt;Responsive&lt;/strong&gt; because I am filming just myself and not a crowd so the auto focus should act as soon as it recognizes a (my) movement&lt;/li&gt;
&lt;li&gt;Movie → Movie2 → &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002273555.html" rel="noopener noreferrer"&gt;AF drive speed&lt;/a&gt; → &lt;strong&gt;Normal&lt;/strong&gt; if you don't move much (don't do sports) in front of your camera to keep the automatic focus adjusment to a minimum&lt;/li&gt;
&lt;li&gt;Setup → Setup2 → &lt;a href="https://helpguide.sony.net/ilc/1810/v1/en/contents/TP0002278042.html" rel="noopener noreferrer"&gt;Auto Power OFF Temp.&lt;/a&gt; → &lt;strong&gt;High&lt;/strong&gt; to allow your camera to get hotter during recording without shutting down&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I use the &lt;strong&gt;control dial&lt;/strong&gt; to set my &lt;strong&gt;aperture&lt;/strong&gt; to the lowest value possible (in my case F1.4 or ƒ/1.4) to get a bright picture with a nice Bokeh effect. I can recommend you the &lt;a href="https://dofsimulator.net/en/" rel="noopener noreferrer"&gt;DOF Simulator&lt;/a&gt; to test different focal lengths and settings without a real camera:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1ahj05ip0dj1tqfpue2.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%2Ff1ahj05ip0dj1tqfpue2.png" alt="DOF Simulator"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recording
&lt;/h2&gt;

&lt;p&gt;If everything is set correctly, you can use your favorite recording software and capture your "Cam Link 4K" device. I am recording my video stream with Camtasia 2021. My audio is being recorded with my Shure SM7B but that's another story.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmj1wais9r484cjo49wnb.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%2Fmj1wais9r484cjo49wnb.png" alt="Camtasia 2021"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Razer Ripsaw X
&lt;/h2&gt;

&lt;p&gt;I want to report that I have also tried a &lt;a href="https://www.razer.com/streaming-capture-cards/razer-ripsaw-x" rel="noopener noreferrer"&gt;Razer Ripsaw X&lt;/a&gt; which doesn't come with any software but relies on automatic resolution detection. It failed to record 4K video for me and only allowed me to capture Full HD (1920x1080) resolution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;Videos that helped me with my settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=4wJGSiRPkG4" rel="noopener noreferrer"&gt;How to Set Up Elgato Cam Link 4K with Sony A6400&lt;/a&gt; from Elgato&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=E1Af6vwoFqM" rel="noopener noreferrer"&gt;Configure SONY cameras for LIVE STREAMING (2020)&lt;/a&gt; from Nilson1489&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=58ok-_eodq8" rel="noopener noreferrer"&gt;Sony a6400 Tutorial&lt;/a&gt; from Think Media&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>recording</category>
      <category>tutorials</category>
      <category>youtube</category>
      <category>video</category>
    </item>
    <item>
      <title>Getting started with GitHub Actions and workflows</title>
      <dc:creator>Benny Code</dc:creator>
      <pubDate>Sun, 03 Apr 2022 12:55:37 +0000</pubDate>
      <link>https://forem.com/typescripttv/getting-started-with-github-actions-and-workflows-2ki2</link>
      <guid>https://forem.com/typescripttv/getting-started-with-github-actions-and-workflows-2ki2</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; are a great way to automate your own software development cycle. GitHub Actions are free of charge for public repositories and provide you with a whole CI/CD platform. It allows you to automate all parts of your software supply chain and run it in virtual environments or even your own environment using &lt;a href="https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners" rel="noopener noreferrer"&gt;self-hosted runners&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Much of what used to be done with a Jenkins job can now be done with GitHub Actions. In this article, I will give you a quick start in GitHub Actions and explain what &lt;strong&gt;actions&lt;/strong&gt;, &lt;strong&gt;workflows&lt;/strong&gt;, &lt;strong&gt;events&lt;/strong&gt;, &lt;strong&gt;jobs&lt;/strong&gt; and &lt;strong&gt;steps&lt;/strong&gt; are. As an example we take a JavaScript application for which we set up a test automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are GitHub Actions?
&lt;/h2&gt;

&lt;p&gt;GitHub Actions are reusable scripts that can be used on GitHub's platform for &lt;strong&gt;continuous integration&lt;/strong&gt; and &lt;strong&gt;continuous delivery&lt;/strong&gt; (CI/CD). You can write your own actions using JavaScript (and other languages) or use published actions from the &lt;a href="https://github.com/marketplace?type=actions" rel="noopener noreferrer"&gt;GitHub Marketplace&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;There are already actions for various tasks like sending a message to a Slack channel (&lt;a href="https://github.com/marketplace/actions/slack-send" rel="noopener noreferrer"&gt;slack-send&lt;/a&gt;), uploading code coverage reports (&lt;a href="https://github.com/marketplace/actions/codecov" rel="noopener noreferrer"&gt;codecov&lt;/a&gt;) or deploying code to the Google Cloud (&lt;a href="https://github.com/marketplace/actions/set-up-gcloud-cloud-sdk-environment" rel="noopener noreferrer"&gt;setup-gcloud&lt;/a&gt;). In this tutorial, we will use existing GitHub Actions and wire them together in a so-called "workflow".&lt;/p&gt;

&lt;h2&gt;
  
  
  What are workflows?
&lt;/h2&gt;

&lt;p&gt;A workflow is a description for your CI/CD pipeline on GitHub Actions. A workflow always runs one or more &lt;strong&gt;jobs&lt;/strong&gt; and each job consists of &lt;strong&gt;steps&lt;/strong&gt; which can be calls to GitHub Actions or regular shell commands. A workflow is triggered by an &lt;strong&gt;event&lt;/strong&gt; (e.g. a commit in your branch) and runs on a virtual environment on GitHub (called "hosted runner") or your own environment (called "self-hosted runner").&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Automation with GitHub Actions
&lt;/h2&gt;

&lt;p&gt;To ensure that pull requests are compatible with your code, you can setup a GitHub workflow to run a test automation pipeline. I will show you how to do this by using a JavaScript demo project for which we will run &lt;code&gt;npm test&lt;/code&gt; when new code comes in.&lt;/p&gt;

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

&lt;p&gt;Setting up a workflow is done by creating a YAML file inside of the &lt;code&gt;.github/workflows&lt;/code&gt; directory of your repository on GitHub. We will save our test automation in &lt;code&gt;test.yml&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;.github/workflows/test.yml&lt;/strong&gt;&lt;/p&gt;

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

&lt;span class="c1"&gt;# Name of our workflow&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Test'&lt;/span&gt;

&lt;span class="c1"&gt;# Events that will trigger our workflow&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pull_request'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;push'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# List of custom jobs&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Job is called "test"&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Using a "label" to assign job to a specific hosted runner&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Checks-out our repository under "$GITHUB_WORKSPACE", so our job can access it&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Checkout&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repository'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="c1"&gt;# Runs commands using the runners shell&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tests'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci &amp;amp;&amp;amp; npm test&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Specify Node.js version
&lt;/h3&gt;

&lt;p&gt;GitHub provides &lt;a href="https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners" rel="noopener noreferrer"&gt;hosted runners&lt;/a&gt; which can run your workflow in different &lt;a href="https://github.com/actions/virtual-environments" rel="noopener noreferrer"&gt;virtual environments&lt;/a&gt;. The "ubuntu-latest" environment already contains a recent version of Node.js which is ideal for testing JavaScript applications.&lt;/p&gt;

&lt;p&gt;You can also use the &lt;a href="https://github.com/actions/setup-node" rel="noopener noreferrer"&gt;setup-node action&lt;/a&gt; to configure any Node.js version you like to use:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Test'&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pull_request'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;push'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Using a build matrix to route workflow to hosted runner(s)&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.os }}&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntu-latest'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;16.x'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;steps&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Checkout&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repository'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="c1"&gt;# Uses specific version of Node.js&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Node.js&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;v${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;matrix.node-version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.node-version }}&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tests'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci &amp;amp;&amp;amp; npm test&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Define workflow triggers
&lt;/h3&gt;

&lt;p&gt;Currently our workflow is executed on every &lt;code&gt;git push&lt;/code&gt; event and every event in a Pull Request. Pushing commits in a PR triggers our action twice because it is a push event and an event in our PR. To prevent this, we can restrict the events that trigger our workflow. We will limit the push events to the "main" branch, which is useful when we squash and merge a PR into our "main" branch:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Test'&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Limit push events to "main" branch&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;main'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.os }}&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntu-latest'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;16.x'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;steps&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Checkout&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repository'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Node.js&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;v${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;matrix.node-version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.node-version }}&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tests'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci &amp;amp;&amp;amp; npm test&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Simply leave the value for &lt;code&gt;pull_request&lt;/code&gt; empty to match any branch name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run workflow manually with &lt;code&gt;workflow_dispatch&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;We can also define a &lt;code&gt;workflow_dispatch&lt;/code&gt; trigger which will allow us to run a workflow manually from the "Actions" tab of our repository:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Test'&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;main'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="c1"&gt;# The "workflow_dispatch" event gives us a button in GitHub's "Action" UI&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.os }}&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntu-latest'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;16.x'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;steps&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Checkout&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repository'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Node.js&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;v${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;matrix.node-version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.node-version }}&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tests'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci &amp;amp;&amp;amp; npm test&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Screenshot:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftas45tcdz8v3ogomr2zk.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%2Ftas45tcdz8v3ogomr2zk.png" alt="Workflow dispatch in action"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Run multiline shell commands
&lt;/h3&gt;

&lt;p&gt;When working with TypeScript, it is advisable to check the validity of your types before running tests. This way errors can be catched even before setting up the test runner. We will accomplish this by running &lt;code&gt;tsc --noEmit&lt;/code&gt; just before executing our test script. In order to have a better overview of our commands, we will replace the &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; link with a multiline command using the pipe (&lt;code&gt;|&lt;/code&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Test'&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;main'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.os }}&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntu-latest'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;16.x'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;steps&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Checkout&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repository'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Node.js&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;v${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;matrix.node-version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.node-version }}&lt;/span&gt;

      &lt;span class="c1"&gt;# Runs multiple commands using the "|" operator&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tests'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm ci&lt;/span&gt;
          &lt;span class="s"&gt;npx tsc --noEmit&lt;/span&gt;
          &lt;span class="s"&gt;npm test&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Skip workflow execution
&lt;/h3&gt;

&lt;p&gt;We can prevent our full test setup from running when adding a specific text (like &lt;code&gt;[skip ci]&lt;/code&gt; or &lt;code&gt;[ci skip]&lt;/code&gt;) in our commit message: &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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Test'&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;main'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.os }}&lt;/span&gt;
    &lt;span class="c1"&gt;# Condition to run the job using GitHub's event API&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;contains(github.event.commits[0].message, '[skip ci]') == false &amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="s"&gt;contains(github.event.commits[0].message, '[ci skip]') == false&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntu-latest'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;16.x'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;steps&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Checkout&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repository'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Node.js&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;v${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;matrix.node-version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.node-version }}&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tests'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm ci&lt;/span&gt;
          &lt;span class="s"&gt;npx tsc --noEmit&lt;/span&gt;
          &lt;span class="s"&gt;npm test&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; By default &lt;a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks#skipping-and-requesting-checks-for-individual-commits" rel="noopener noreferrer"&gt;GitHub skips checks&lt;/a&gt; for commits that have two empty lines followed by &lt;code&gt;skip-checks: true&lt;/code&gt; within the commit message before the closing quotation:&lt;/p&gt;

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

git commit -m "Some commit message
&amp;gt;
&amp;gt;
skip-checks: true"


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Using expressions in workflows
&lt;/h3&gt;

&lt;p&gt;The workflow syntax for GitHub Actions allows us to use &lt;a href="https://docs.github.com/en/actions/learn-github-actions/expressions#about-expressions" rel="noopener noreferrer"&gt;expressions&lt;/a&gt;. There is a set of built-in functions, like &lt;code&gt;success()&lt;/code&gt; and &lt;code&gt;failure()&lt;/code&gt;, that can be used in expressions and are very handy to check the status of your workflow. We will use &lt;code&gt;failure()&lt;/code&gt; to send a message to our Slack channel whenever our tests fail:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Test'&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;main'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.os }}&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;contains(github.event.commits[0].message, '[skip ci]') == false &amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="s"&gt;contains(github.event.commits[0].message, '[ci skip]') == false&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntu-latest'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;16.x'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;steps&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Checkout&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repository'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Node.js&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;v${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;matrix.node-version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.node-version }}&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tests'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm ci&lt;/span&gt;
          &lt;span class="s"&gt;npx tsc --noEmit&lt;/span&gt;
          &lt;span class="s"&gt;npm test&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Post&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;notification&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Slack&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;channel'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;slackapi/slack-github-action@v1.18.0&lt;/span&gt;
        &lt;span class="c1"&gt;# Use built-in function in expression&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ failure() }}&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;channel-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-channel&lt;/span&gt;
          &lt;span class="na"&gt;slack-message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Test&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.server_url&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;github.repository&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/actions/runs/${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.run_id&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;github.run_id&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;failed.'&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SLACK_BOT_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MY_SLACK_BOT_TOKEN }}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; To make use of the Slack Action, you have to &lt;a href="https://api.slack.com/apps" rel="noopener noreferrer"&gt;create a Slack App&lt;/a&gt; for your Slack workspace with an OAuth scope of &lt;code&gt;chat.write&lt;/code&gt;. Afterwards, you have to make your "Bot User OAuth Token" available as environment variable (e.g. &lt;code&gt;MY_SLACK_BOT_TOKEN&lt;/code&gt;) in your GitHub repository. This can be done in Settings → Secrets → Actions. It will then be accessible in your workflow file using the &lt;code&gt;${{ secrets.MY_SLACK_BOT_TOKEN }}&lt;/code&gt; expression.&lt;/p&gt;

&lt;h2&gt;
  
  
  Branch protection rules
&lt;/h2&gt;

&lt;p&gt;Once you have a testing workflow and sufficient tests covering your code, you can setup a branch protection rule. This can be done by navigating to Settings → Branches → Branch protection rules → Add rule in your GitHub repository.&lt;/p&gt;

&lt;p&gt;The "Branch name pattern" supports &lt;a href="https://ruby-doc.org/core-2.5.1/File.html#method-c-fnmatch" rel="noopener noreferrer"&gt;fnmatch syntax&lt;/a&gt; but also allows to set a single branch name (like "main"). In order to protect your branch from incompatible dependency updates you have to activate "Require status checks to pass before merging". You can use GitHub Actions as status checks by searching for their job names (e.g. "test").&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screenshot:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuew1wu0ujgncwndo7yvj.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%2Fuew1wu0ujgncwndo7yvj.png" alt="Branch Protection Rule Setup"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The branch protection rule will warn you when new code fails your testing pipeline. It will also prevent merging broken code into your "main" branch when you are not an administrator who can override such rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running your GitHub Actions locally
&lt;/h2&gt;

&lt;p&gt;If you want to have faster feedback loops, you can also run GitHub Actions locally using the &lt;a href="https://github.com/nektos/act" rel="noopener noreferrer"&gt;act cli&lt;/a&gt;. It requires Docker and a &lt;a href="https://github.com/nektos/act/tree/v0.2.26#installation-through-package-managers" rel="noopener noreferrer"&gt;local installation&lt;/a&gt; through your favorite package manager.&lt;/p&gt;

&lt;p&gt;After installing "act", you can run it locally from your terminal by passing it the job name of your workflow, e.g. &lt;code&gt;act -j test&lt;/code&gt;. It will then download the necessary Docker image. Depending on the complexity of your workflow, this image can be 20+ GB in size. For our small test setup a micro image (below 200 MB) that contains only Node.js is good enough when we remove our "skip ci" condition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screenshot:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbz6v41au6aebfkrb0h6v.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%2Fbz6v41au6aebfkrb0h6v.png" alt="act-cli in action"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go from here?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Congratulations!&lt;/strong&gt; You have just learned the fundamentals of GitHub Actions and you are now able to create your own workflows. With your newly acquired skills, you can build great CI/CD pipelines. 🎊&lt;/p&gt;

&lt;p&gt;If you want to learn more about GitHub Actions, then I recommend the following topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/using-workflows/advanced-workflow-features#creating-dependent-jobs" rel="noopener noreferrer"&gt;Creating dependant jobs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/using-workflows/reusing-workflows" rel="noopener noreferrer"&gt;Reuse workflows to avoid duplication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions" rel="noopener noreferrer"&gt;Use Dependabot with GitHub Actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/w_37LDOy4sI?t=570" rel="noopener noreferrer"&gt;Review deployment jobs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/creating-actions/creating-a-javascript-action#writing-the-action-code" rel="noopener noreferrer"&gt;Create and publish your own actions using JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=SASoUr9X0QA" rel="noopener noreferrer"&gt;Use self-hosted runners with your workflows&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>devops</category>
      <category>github</category>
    </item>
  </channel>
</rss>
