<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vatsal Trivedi</title>
    <description>The latest articles on DEV Community by Vatsal Trivedi (@trivedivatsal).</description>
    <link>https://dev.to/trivedivatsal</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%2F71868%2F9d8c7564-8df8-4d8f-b38a-32983643e051.jpg</url>
      <title>DEV Community: Vatsal Trivedi</title>
      <link>https://dev.to/trivedivatsal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/trivedivatsal"/>
    <language>en</language>
    <item>
      <title>I got tired of manual code reviews so I built a free automated security pipeline</title>
      <dc:creator>Vatsal Trivedi</dc:creator>
      <pubDate>Mon, 23 Mar 2026 07:06:39 +0000</pubDate>
      <link>https://dev.to/trivedivatsal/i-got-tired-of-manual-code-reviews-so-i-built-a-free-automated-security-pipeline-1lmd</link>
      <guid>https://dev.to/trivedivatsal/i-got-tired-of-manual-code-reviews-so-i-built-a-free-automated-security-pipeline-1lmd</guid>
      <description>&lt;p&gt;Let me be upfront about something. For a while, our security process was basically vibes. Someone would glance over a PR, maybe catch an obvious thing, and we'd ship it. No linting enforcement. No dependency scanning. No idea if someone accidentally committed a database password six months ago.&lt;/p&gt;

&lt;p&gt;It wasn't negligence exactly, it was the usual small team problem. Everyone's busy, security tooling feels like a rabbit hole, and the good stuff costs money we didn't want to spend.&lt;/p&gt;

&lt;p&gt;Then I spent an afternoon looking into it properly and realised the entire thing was solvable for free, in a weekend. This is what I set up.&lt;/p&gt;




&lt;h2&gt;
  
  
  The constraints I was working with
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Python (Django) backend, JavaScript/Node.js frontend&lt;/li&gt;
&lt;li&gt;Everything lives on GitHub&lt;/li&gt;
&lt;li&gt;Small team - I'd be the one maintaining this&lt;/li&gt;
&lt;li&gt;Not willing to pay for tooling, at least not yet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last point mattered a lot. Most "enterprise" security tools have pricing pages that just say "contact sales." Hard pass.&lt;/p&gt;




&lt;h2&gt;
  
  
  The approach: three layers, not one big tool
&lt;/h2&gt;

&lt;p&gt;The mistake I almost made was looking for a single tool that does everything. That tool either doesn't exist or costs a lot. Instead, I split the problem into three layers based on how fast feedback needs to be:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 1 — Before the code even leaves my machine.&lt;/strong&gt; Pre-commit hooks. Fast, local, blocks commits instantly if something looks wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 2 — Before the code gets merged.&lt;/strong&gt; GitHub Actions on every PR. Takes a few minutes, catches deeper security issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 3 — The full picture.&lt;/strong&gt; A proper dashboard showing the health of the entire codebase. Runs weekly and after every merge to main.&lt;/p&gt;

&lt;p&gt;Each layer has a different job. Trying to do everything in one place means either slow commit times or shallow analysis. Keeping them separate means you get both speed and depth.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 1: Pre-commit hooks
&lt;/h2&gt;

&lt;p&gt;Install the framework once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pre-commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then drop this &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; in your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/gitleaks/gitleaks&lt;/span&gt;
    &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v8.18.0&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitleaks&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/astral-sh/ruff-pre-commit&lt;/span&gt;
    &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v0.3.0&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruff&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;--fix&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruff-format&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/pre-commit/mirrors-eslint&lt;/span&gt;
    &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v8.57.0&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eslint&lt;/span&gt;
        &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;\.(js|jsx|ts|tsx)$&lt;/span&gt;
        &lt;span class="na"&gt;additional_dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;eslint@8.57.0&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;eslint-plugin-security@1.7.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And activate it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pre-commit &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole setup. Every developer runs &lt;code&gt;pre-commit install&lt;/code&gt; once and then forgets about it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's actually running here
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Gitleaks&lt;/strong&gt; scans every commit for secrets - API keys, database URLs, tokens, anything that looks like a credential. If it finds one, the commit gets blocked before it goes anywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This commit will NOT go through
&lt;/span&gt;&lt;span class="n"&gt;STRIPE_SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk_live_abc123xyz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;DATABASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres://user:password@prod-host/db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've seen codebases where dev credentials had been sitting in git history for years. This stops that from happening in the first place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ruff&lt;/strong&gt; handles Python linting and formatting. It's genuinely fast - replaces flake8, black, and isort in one tool. The &lt;code&gt;--fix&lt;/code&gt; flag means it auto-corrects most things silently. You barely notice it running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ESLint with the security plugin&lt;/strong&gt; catches JavaScript issues that normal linting misses, like using &lt;code&gt;eval()&lt;/code&gt; with dynamic input or building RegEx from user-supplied strings (a classic ReDoS vector).&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 2: GitHub Actions on pull requests
&lt;/h2&gt;

&lt;p&gt;Every PR against &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;develop&lt;/code&gt; triggers two scanners automatically. The results show up directly in the GitHub Security tab on the PR - no context switching, no separate dashboard to check.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;.github/workflows/security-scan.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Security Scan&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;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;develop&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;trivy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dependency Vulnerability Scan&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="pi"&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/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Trivy&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;aquasecurity/trivy-action@master&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;scan-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fs&lt;/span&gt;
          &lt;span class="na"&gt;scan-ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
          &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HIGH,CRITICAL&lt;/span&gt;
          &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sarif&lt;/span&gt;
          &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trivy-results.sarif&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload to GitHub Security tab&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;github/codeql-action/upload-sarif@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;sarif_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trivy-results.sarif&lt;/span&gt;

  &lt;span class="na"&gt;bandit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Python Security Scan&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="pi"&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/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&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-python@v5&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;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.11'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install and run Bandit&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;pip install bandit[toml]&lt;/span&gt;
          &lt;span class="s"&gt;bandit -r . \&lt;/span&gt;
            &lt;span class="s"&gt;-x ./node_modules,./venv,./.venv,./tests \&lt;/span&gt;
            &lt;span class="s"&gt;-f json \&lt;/span&gt;
            &lt;span class="s"&gt;-o bandit-report.json || true&lt;/span&gt;

      &lt;span class="pi"&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/upload-artifact@v4&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bandit-security-report&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bandit-report.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why these two tools specifically
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Trivy&lt;/strong&gt; scans &lt;code&gt;requirements.txt&lt;/code&gt; and &lt;code&gt;package.json&lt;/code&gt; against known CVE databases. When it finds something, it tells you the exact version you have, the CVE ID, the severity, and what version fixes it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CRITICAL  CVE-2023-1234  cryptography 3.4.6 → upgrade to 41.0.0
HIGH      CVE-2023-5678  Pillow 9.0.0 → upgrade to 10.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's actionable. No digging through changelogs to figure out if you're affected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bandit&lt;/strong&gt; understands Python and Django patterns specifically. It catches the stuff that generic linters miss - things like Django views doing raw SQL string concatenation, &lt;code&gt;DEBUG = True&lt;/code&gt; making it into a non-dev file, or &lt;code&gt;subprocess&lt;/code&gt; calls using &lt;code&gt;shell=True&lt;/code&gt;. If you've worked on a Django codebase for a while you've probably seen all of these in the wild.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 3: SonarQube for the full codebase picture
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting. The previous two layers are reactive - they catch issues in code you're actively changing. SonarQube gives you a view of the entire codebase, including the stuff nobody's touched in two years.&lt;/p&gt;

&lt;p&gt;One thing worth knowing before you go down the Semgrep route for this: Semgrep's free (Community) edition only analyzes code within a single function or file. It can't follow data across your application. SonarQube's community build does full cross-file analysis, which is what you actually want for a codebase-wide report.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spin it up locally first
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; sonarqube &lt;span class="nt"&gt;-p&lt;/span&gt; 9000:9000 sonarqube:community
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to &lt;code&gt;http://localhost:9000&lt;/code&gt;, log in with &lt;code&gt;admin&lt;/code&gt;/&lt;code&gt;admin&lt;/code&gt;, and immediately change the password. Create a project, grab a token, and add &lt;code&gt;sonar-project.properties&lt;/code&gt; to your repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;sonar.projectKey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;your-project-name&lt;/span&gt;
&lt;span class="py"&gt;sonar.projectName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Your Project Name&lt;/span&gt;
&lt;span class="py"&gt;sonar.projectVersion&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1.0&lt;/span&gt;

&lt;span class="py"&gt;sonar.sources&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;
&lt;span class="py"&gt;sonar.exclusions&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;**/node_modules/**,**/venv/**,**/.venv/**,**/migrations/**,**/*.min.js&lt;/span&gt;

&lt;span class="py"&gt;sonar.python.version&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3.11&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Then automate it
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;.github/workflows/sonarqube.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SonarQube Full Scan&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;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="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&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;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1'&lt;/span&gt;  &lt;span class="c1"&gt;# Monday 2am&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;sonarqube&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;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="pi"&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/checkout@v4&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;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# full history = better analysis&lt;/span&gt;

      &lt;span class="pi"&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;SonarSource/sonarqube-scan-action@master&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;SONAR_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SONAR_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;SONAR_HOST_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SONAR_HOST_URL }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add both secrets under &lt;strong&gt;Settings → Secrets → Actions&lt;/strong&gt; in your GitHub repo.&lt;/p&gt;

&lt;p&gt;Now it runs automatically every Monday and every time something merges to main. You just check the dashboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the first scan actually found
&lt;/h2&gt;

&lt;p&gt;I want to be honest about this because I've read too many posts that describe a perfect greenfield setup. This was a real codebase that had been built by humans under time pressure.&lt;/p&gt;

&lt;p&gt;The first SonarQube scan came back with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hardcoded dev credentials that had been in the repo for months. Not production secrets, but still - they had no business being there.&lt;/li&gt;
&lt;li&gt;A couple of Django views building SQL with string formatting instead of parameterized queries. Classic, boring, dangerous.&lt;/li&gt;
&lt;li&gt;Several npm packages flagged by Trivy with HIGH severity CVEs, all with fixes available. Literally just needed version bumps.&lt;/li&gt;
&lt;li&gt;A significant chunk of duplicated logic copied between files that had clearly diverged over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of it was a disaster. But any of it could have become one. The SQL stuff in particular - that's the kind of thing that sits quietly in a codebase until someone thinks to try it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling the noise
&lt;/h2&gt;

&lt;p&gt;The first scan will surface a lot. Don't try to fix everything at once - you'll burn out and give up on the tooling entirely.&lt;/p&gt;

&lt;p&gt;The priority order that works for us:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;What we do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🔴 Critical&lt;/td&gt;
&lt;td&gt;Don't merge. Fix it now.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🟠 High&lt;/td&gt;
&lt;td&gt;Fix it this sprint.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🟡 Medium&lt;/td&gt;
&lt;td&gt;Goes on the backlog.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🟢 Low&lt;/td&gt;
&lt;td&gt;Fix it when you're touching that code anyway.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For SonarQube false positives - and there will be some - mark them as "Won't Fix" and add a comment explaining why. Dismissing without a comment just creates confusion later.&lt;/p&gt;




&lt;h2&gt;
  
  
  The ongoing cost
&lt;/h2&gt;

&lt;p&gt;Once this is set up, the maintenance is genuinely minimal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pre-commit hooks run themselves. Zero maintenance.&lt;/li&gt;
&lt;li&gt;GitHub Actions trigger themselves. Zero maintenance.&lt;/li&gt;
&lt;li&gt;SonarQube dashboard - I spend maybe 20-30 minutes a week looking at it.&lt;/li&gt;
&lt;li&gt;Once a month, run &lt;code&gt;pre-commit autoupdate&lt;/code&gt; to pull in newer tool versions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. The pipeline runs whether I'm thinking about it or not, which is exactly the point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to start if this seems like a lot
&lt;/h2&gt;

&lt;p&gt;You don't need to do all of this at once. The pre-commit hooks alone are worth doing today - &lt;code&gt;pip install pre-commit&lt;/code&gt;, drop in the config, run &lt;code&gt;pre-commit install&lt;/code&gt;, and Gitleaks starts protecting your commits immediately. Takes maybe 20 minutes.&lt;/p&gt;

&lt;p&gt;The GitHub Actions workflow is the next step, and it's mostly copy-paste. SonarQube requires the most setup but gives the most visibility.&lt;/p&gt;

&lt;p&gt;Each layer is independent. Pick the one that solves your most pressing problem first and add the others when you're ready.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.sonarsource.com/open-source-editions/sonarqube-community-edition/" rel="noopener noreferrer"&gt;SonarQube Community Build&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://trivy.dev/" rel="noopener noreferrer"&gt;Trivy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bandit.readthedocs.io/" rel="noopener noreferrer"&gt;Bandit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitleaks.io/" rel="noopener noreferrer"&gt;Gitleaks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.astral.sh/ruff/" rel="noopener noreferrer"&gt;Ruff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/eslint-community/eslint-plugin-security" rel="noopener noreferrer"&gt;eslint-plugin-security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pre-commit.com/" rel="noopener noreferrer"&gt;pre-commit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Everything here is free. Nothing leaves your infrastructure. If you set this up and find something interesting on the first scan, I'd be curious to hear about it.&lt;/p&gt;

</description>
      <category>devsecops</category>
      <category>python</category>
      <category>javascript</category>
      <category>github</category>
    </item>
    <item>
      <title>The "Lazy" Developer’s Guide to Twilio Auto-Replies (No Server Required)</title>
      <dc:creator>Vatsal Trivedi</dc:creator>
      <pubDate>Mon, 09 Mar 2026 10:44:25 +0000</pubDate>
      <link>https://dev.to/trivedivatsal/the-lazy-developers-guide-to-twilio-auto-replies-no-server-required-bmh</link>
      <guid>https://dev.to/trivedivatsal/the-lazy-developers-guide-to-twilio-auto-replies-no-server-required-bmh</guid>
      <description>&lt;p&gt;When building messaging workflows, one of the most common requirements is sending a &lt;strong&gt;default response when someone messages your number&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Normally this involves setting up a webhook, deploying a backend service, and writing some code to respond to incoming messages.&lt;/p&gt;

&lt;p&gt;However, if your use case is simple — like sending a &lt;strong&gt;welcome message or acknowledgement&lt;/strong&gt; — you can skip the backend entirely by using &lt;strong&gt;Twilio&lt;/strong&gt; with &lt;strong&gt;TwiML&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Using &lt;strong&gt;TwiML Bins&lt;/strong&gt;, you can configure automatic responses directly inside the &lt;strong&gt;Twilio Console&lt;/strong&gt;, with no external server.&lt;/p&gt;

&lt;p&gt;Let’s walk through how to do it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Idea Behind This Setup
&lt;/h2&gt;

&lt;p&gt;Instead of sending incoming messages to your own application, you can have Twilio call a &lt;strong&gt;TwiML endpoint hosted by Twilio itself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That endpoint simply returns instructions telling Twilio to send a message back.&lt;/p&gt;

&lt;p&gt;The message flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User sends a message
        ↓
Twilio receives the message
        ↓
Twilio calls a TwiML Bin
        ↓
The TwiML response tells Twilio to send a reply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s a very clean setup and perfect for quick messaging workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Create a TwiML Bin
&lt;/h2&gt;

&lt;p&gt;Start by creating a TwiML Bin.&lt;/p&gt;

&lt;p&gt;Inside the &lt;strong&gt;Twilio Console&lt;/strong&gt;, go to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Develop → Runtime → TwiML Bins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new TwiML Bin and add the following TwiML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Response&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Message&amp;gt;&lt;/span&gt;
Hi 👋

Welcome!

You will now receive important updates and notifications through this messaging channel.

Thanks for connecting with us.
  &lt;span class="nt"&gt;&amp;lt;/Message&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Response&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the configuration.&lt;/p&gt;

&lt;p&gt;Once saved, Twilio will generate a &lt;strong&gt;handler URL&lt;/strong&gt; that looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://handler.twilio.com/twiml/EHxxxxxxxxxxxxxxxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This URL acts as a small hosted endpoint that returns your TwiML instructions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — Configure Incoming Messages
&lt;/h2&gt;

&lt;p&gt;Next, tell Twilio to call this TwiML endpoint whenever a message arrives.&lt;/p&gt;

&lt;p&gt;Open your &lt;strong&gt;Messaging Service&lt;/strong&gt; settings and navigate to the &lt;strong&gt;Integration&lt;/strong&gt; section.&lt;/p&gt;

&lt;p&gt;Under &lt;strong&gt;Incoming Messages&lt;/strong&gt;, select:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Send a webhook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then paste your TwiML Bin URL into the &lt;strong&gt;Request URL&lt;/strong&gt; field.&lt;/p&gt;

&lt;p&gt;Example configuration:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Incoming Messages&lt;/td&gt;
&lt;td&gt;Send a webhook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request URL&lt;/td&gt;
&lt;td&gt;TwiML Bin handler URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP Method&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Save the changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Test the Flow
&lt;/h2&gt;

&lt;p&gt;Now try sending a message to your Twilio number.&lt;/p&gt;

&lt;p&gt;If everything is configured correctly, this will happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A user sends a message to your number&lt;/li&gt;
&lt;li&gt;Twilio receives it&lt;/li&gt;
&lt;li&gt;Twilio calls the TwiML Bin&lt;/li&gt;
&lt;li&gt;The TwiML response instructs Twilio to send a reply&lt;/li&gt;
&lt;li&gt;The user immediately receives the welcome message&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No backend services are involved.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding the TwiML
&lt;/h2&gt;

&lt;p&gt;The TwiML used here is intentionally simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Response&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Message&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/Message&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Response&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;&amp;lt;Response&amp;gt;&lt;/code&gt; is the root element that Twilio expects.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;Message&amp;gt;&lt;/code&gt; instructs Twilio to send a message back to the sender.&lt;/p&gt;

&lt;p&gt;When Twilio receives this XML response, it executes the instruction automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Approach Works Well
&lt;/h2&gt;

&lt;p&gt;Using TwiML Bins is ideal when you want something &lt;strong&gt;simple and fast&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It works well for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;welcome messages&lt;/li&gt;
&lt;li&gt;confirmation replies&lt;/li&gt;
&lt;li&gt;acknowledgement responses&lt;/li&gt;
&lt;li&gt;business hour notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since everything is hosted inside Twilio, there’s &lt;strong&gt;no infrastructure to maintain&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  When You Might Need Something More Advanced
&lt;/h2&gt;

&lt;p&gt;TwiML Bins are great for static responses, but they are intentionally limited.&lt;/p&gt;

&lt;p&gt;If your workflow requires things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;conditional logic&lt;/li&gt;
&lt;li&gt;menu systems&lt;/li&gt;
&lt;li&gt;chatbot flows&lt;/li&gt;
&lt;li&gt;database lookups&lt;/li&gt;
&lt;li&gt;integration with other services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then you’ll likely want to use &lt;strong&gt;Twilio Studio&lt;/strong&gt; or build a webhook-based backend.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;TwiML Bins are one of the fastest ways to get an &lt;strong&gt;auto-reply system up and running&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For simple messaging workflows, they remove the need for backend infrastructure while still giving you full control over the response sent to users.&lt;/p&gt;

&lt;p&gt;If you just need to greet users or confirm that their message was received, this approach can save a lot of development time.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Fix the NVM for Windows `NVM_SYMLINK` Activation Error</title>
      <dc:creator>Vatsal Trivedi</dc:creator>
      <pubDate>Wed, 04 Mar 2026 11:17:15 +0000</pubDate>
      <link>https://dev.to/trivedivatsal/fixing-the-nvmsymlink-is-set-to-a-physical-filedirectory-error-on-windows-21hp</link>
      <guid>https://dev.to/trivedivatsal/fixing-the-nvmsymlink-is-set-to-a-physical-filedirectory-error-on-windows-21hp</guid>
      <description>&lt;p&gt;If you're using &lt;strong&gt;NVM for Windows&lt;/strong&gt; and see the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nvm enabled activation error:
NVM_SYMLINK is set to a physical file/directory at C:\Program Files\nodejs
Please remove the location and try again,
or select a different location for NVM_SYMLINK.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're not alone. This is a common issue when switching from a traditional Node.js installation to NVM.&lt;/p&gt;

&lt;p&gt;This guide explains the root cause and provides a guaranteed step-by-step fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Error Happens
&lt;/h2&gt;

&lt;p&gt;When Node.js is installed using the official Windows installer, it creates this directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\Program Files\nodejs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, &lt;strong&gt;NVM for Windows&lt;/strong&gt; uses this same path as a symbolic link (&lt;code&gt;NVM_SYMLINK&lt;/code&gt;) to dynamically switch between Node versions.&lt;/p&gt;

&lt;p&gt;If a physical directory already exists there, NVM cannot override it — and activation fails.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step-by-Step Fix (Guaranteed Method)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Close All Node-Related Applications
&lt;/h3&gt;

&lt;p&gt;Before making changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Close all terminal windows&lt;/li&gt;
&lt;li&gt;Close VS Code or any IDE&lt;/li&gt;
&lt;li&gt;Stop any running Node.js applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents file locking issues.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2: Open Command Prompt as Administrator
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Press &lt;strong&gt;Start&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Type &lt;code&gt;cmd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Right-click → &lt;strong&gt;Run as Administrator&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Administrator privileges are required because we are modifying &lt;code&gt;Program Files&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3: Take Ownership of the Directory
&lt;/h3&gt;

&lt;p&gt;Windows may block deletion due to TrustedInstaller permissions.&lt;/p&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;takeown /f "C:\Program Files\nodejs" /r /d y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then grant full permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;icacls "C:\Program Files\nodejs" /grant %username%:F /t
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 4: Kill Any Running Node Processes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;taskkill /f /im node.exe
taskkill /f /im npm.exe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures no processes are locking files.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 5: Delete the Existing Node Directory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rmdir /s /q "C:\Program Files\nodejs"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Command flags explained:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/s&lt;/code&gt; → Deletes all subdirectories and files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/q&lt;/code&gt; → Suppresses confirmation prompts&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Still Getting “Access is Denied”?
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;PowerShell as Administrator&lt;/strong&gt; and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Program Files\nodejs"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Fallback Method (If Files Are Locked)
&lt;/h2&gt;

&lt;p&gt;If the directory still refuses to delete:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Restart your system&lt;/li&gt;
&lt;li&gt;Do not open any applications&lt;/li&gt;
&lt;li&gt;Immediately open Command Prompt as Administrator&lt;/li&gt;
&lt;li&gt;Run:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rmdir /s /q "C:\Program Files\nodejs"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This resolves most background file lock issues.&lt;/p&gt;




&lt;h2&gt;
  
  
  Re-Enable NVM
&lt;/h2&gt;

&lt;p&gt;Once the directory is removed:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then install and activate a Node version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nvm install 18
nvm use 18
node -v
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now see the installed Node version.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best Practice When Migrating to NVM
&lt;/h2&gt;

&lt;p&gt;If you're switching from a direct Node installation to NVM:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Uninstall Node.js from Control Panel first&lt;/li&gt;
&lt;li&gt;Manually verify that &lt;code&gt;C:\Program Files\nodejs&lt;/code&gt; is deleted&lt;/li&gt;
&lt;li&gt;Then install and configure NVM&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This prevents activation conflicts entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This error occurs because NVM relies on symbolic linking, and Windows does not allow it to overwrite an existing physical directory.&lt;/p&gt;

&lt;p&gt;Following the above steps will completely resolve the &lt;code&gt;NVM_SYMLINK&lt;/code&gt; activation error in most Windows environments.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>tooling</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>From MySQL to PostgreSQL: My Django Migration Story (and a Tool to Help)</title>
      <dc:creator>Vatsal Trivedi</dc:creator>
      <pubDate>Tue, 07 Oct 2025 13:35:07 +0000</pubDate>
      <link>https://dev.to/trivedivatsal/from-mysql-to-postgresql-my-django-migration-story-and-a-tool-to-help-1k8m</link>
      <guid>https://dev.to/trivedivatsal/from-mysql-to-postgresql-my-django-migration-story-and-a-tool-to-help-1k8m</guid>
      <description>&lt;p&gt;Every project grows over time. You start with tools that work great in the beginning, but as your app and data grow, your needs change. That’s what happened to me. My Django app was built on MySQL, and it worked well for a long time. But as things scaled up, I realized I needed something more powerful and flexible.&lt;/p&gt;

&lt;p&gt;So, I decided to move to PostgreSQL - a database known for better performance, advanced features, and scalability. But database migration is not an easy task. I had to make sure the data stayed safe, handle different data types between MySQL and PostgreSQL, and make the whole process repeatable and reliable.&lt;/p&gt;

&lt;p&gt;Doing this manually with commands and scripts felt messy and risky. So, I decided to create a clean and reusable solution.&lt;/p&gt;




&lt;h3&gt;
  
  
  My Solution: A Toolkit for Django Migrations
&lt;/h3&gt;

&lt;p&gt;To make the migration process easier, I built a small toolkit and wrote a step-by-step guide. I’ve open-sourced everything so that others can use it too.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;GitHub Repository:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/trivedi-vatsal/django-mysql-to-postgres" rel="noopener noreferrer"&gt;&lt;strong&gt;https://github.com/trivedi-vatsal/django-mysql-to-postgres&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The toolkit automates most of the hard work. The main script transfers your database schema and data from MySQL to PostgreSQL and maps the data types correctly.&lt;/p&gt;

&lt;p&gt;I also added a few helper tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;connection tester&lt;/strong&gt; to check your new database settings before starting.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;&lt;code&gt;--dry-run&lt;/code&gt;&lt;/strong&gt; option to test the migration safely without changing real data.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;cleanup script&lt;/strong&gt; to remove old migration files after switching over.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  My Approach
&lt;/h3&gt;

&lt;p&gt;My main goal was safety and confidence. I made sure the process included steps like taking backups, testing connections, and running a dry run first. That way, you can migrate without worrying about data loss or downtime.&lt;/p&gt;

&lt;p&gt;The guide in the repository explains everything - the commands, the scripts, and how to fix common issues. It’s based on my own migration experience, turned into a simple plan you can follow.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why I’m Sharing This
&lt;/h3&gt;

&lt;p&gt;Switching from MySQL to PostgreSQL was a big improvement for my project. It helped me scale faster and manage data more effectively.&lt;/p&gt;

&lt;p&gt;If you’re planning to move your Django project to PostgreSQL, check out the repo. I’d love to hear your feedback or how it works for you.&lt;/p&gt;

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

</description>
      <category>postgres</category>
      <category>django</category>
      <category>database</category>
      <category>tooling</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Vatsal Trivedi</dc:creator>
      <pubDate>Sun, 05 Oct 2025 10:52:13 +0000</pubDate>
      <link>https://dev.to/trivedivatsal/-46bl</link>
      <guid>https://dev.to/trivedivatsal/-46bl</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/trivedivatsal/my-pinch-of-salt-did-ai-and-llms-kill-the-first-time-correct-engineer-3p9f" class="crayons-story__hidden-navigation-link"&gt;My Pinch of Salt: Did AI and LLMs Kill the "First-Time Correct" Engineer?&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/trivedivatsal" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F71868%2F9d8c7564-8df8-4d8f-b38a-32983643e051.jpg" alt="trivedivatsal profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/trivedivatsal" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Vatsal Trivedi
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Vatsal Trivedi
                
              
              &lt;div id="story-author-preview-content-2866203" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/trivedivatsal" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F71868%2F9d8c7564-8df8-4d8f-b38a-32983643e051.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Vatsal Trivedi&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/trivedivatsal/my-pinch-of-salt-did-ai-and-llms-kill-the-first-time-correct-engineer-3p9f" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 24 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/trivedivatsal/my-pinch-of-salt-did-ai-and-llms-kill-the-first-time-correct-engineer-3p9f" id="article-link-2866203"&gt;
          My Pinch of Salt: Did AI and LLMs Kill the "First-Time Correct" Engineer?
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/discuss"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;discuss&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/coding"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;coding&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/llm"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;llm&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/trivedivatsal/my-pinch-of-salt-did-ai-and-llms-kill-the-first-time-correct-engineer-3p9f" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;5&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/trivedivatsal/my-pinch-of-salt-did-ai-and-llms-kill-the-first-time-correct-engineer-3p9f#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




</description>
      <category>ai</category>
      <category>discuss</category>
      <category>coding</category>
      <category>llm</category>
    </item>
    <item>
      <title>Leaving Breadcrumbs for Your AI: The Hansel and Gretel Approach</title>
      <dc:creator>Vatsal Trivedi</dc:creator>
      <pubDate>Tue, 30 Sep 2025 09:11:39 +0000</pubDate>
      <link>https://dev.to/trivedivatsal/leaving-breadcrumbs-for-your-ai-the-hansel-and-gretel-approach-132f</link>
      <guid>https://dev.to/trivedivatsal/leaving-breadcrumbs-for-your-ai-the-hansel-and-gretel-approach-132f</guid>
      <description>&lt;p&gt;I recently discovered a game-changing technique from Giuseppe Gurgone's article: &lt;strong&gt;comment directives&lt;/strong&gt;. It's fundamentally simplified how I work with AI coding assistants, calling it "leaving breadcrumbs" for my AI, much like &lt;strong&gt;Hansel and Gretel&lt;/strong&gt; guiding their way through the woods.&lt;/p&gt;

&lt;p&gt;Instead of writing massive, detailed prompts in my terminal, I now embed instructions directly into my code using special comments. The context stays exactly where it's needed, which is a huge win for efficiency.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Go-To: The &lt;code&gt;@implement&lt;/code&gt; Directive
&lt;/h3&gt;

&lt;p&gt;The core of this workflow is the &lt;code&gt;@implement&lt;/code&gt; directive.&lt;/p&gt;

&lt;p&gt;When I plan an enhancement, but don't want to implement it yet, I drop a directive like this right into the file:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example Directive:&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;class&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* @implement
   * Add a caching layer for user data:
   * - Cache user objects by ID in a Map
   * - Expire entries after 5 minutes
   * - Return cached data if available and fresh
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Current implementation...&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;Later, I just tell my AI (like Claude Code): "Implement all the &lt;code&gt;@implement&lt;/code&gt; directives."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I Love It:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Context is Local:&lt;/strong&gt; The AI has all the specific instructions right next to the code signature it needs to change.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Instant Documentation:&lt;/strong&gt; The AI doesn't just write the code; it converts the directive into proper JSDoc (or similar documentation), ensuring the code is clean and explained immediately.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Beyond Implementation: &lt;code&gt;@docs&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The pattern is flexible. I use the &lt;code&gt;@docs&lt;/code&gt; directive to provide external context for the AI:&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="cm"&gt;/*
 * This component renders a list of products using suspense.
 * @docs https://react.dev/reference/react/Suspense
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures the AI is aware of the necessary technical background without me needing to manually fetch and paste links.&lt;/p&gt;

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

&lt;p&gt;I highly recommend trying this. It moves instructions out of a separate task list and directly into your source code, creating a much smoother, human-centered development experience with AI.&lt;/p&gt;

&lt;p&gt;I found this method from here: &lt;a href="https://giuseppegurgone.com/comment-directives-claude-code" rel="noopener noreferrer"&gt;Comment Directives for Claude Code&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Note: The AI-assisted coding space is changing fast. Always validate techniques for your specific use case.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>tooling</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>The Cleanest Way to Handle Unused Variables in TypeScript</title>
      <dc:creator>Vatsal Trivedi</dc:creator>
      <pubDate>Mon, 29 Sep 2025 15:58:14 +0000</pubDate>
      <link>https://dev.to/trivedivatsal/the-cleanest-way-to-handle-unused-variables-in-typescript-345</link>
      <guid>https://dev.to/trivedivatsal/the-cleanest-way-to-handle-unused-variables-in-typescript-345</guid>
      <description>&lt;p&gt;We've all been there. You're deep in the zone, maybe mapping over an array, setting up an Express middleware, or defining a React component. You’re required to accept a parameter you don’t actually need, and like clockwork, your linter starts complaining:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;'props' is declared but its value is never read. @typescript-eslint/no-unused-vars&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;What’s your gut reaction? Do you litter the code with &lt;code&gt;// eslint-disable-next-line&lt;/code&gt; comments, creating technical debt? Do you surrender and weaken a valuable rule in your config? Or do you just learn to ignore the yellow squiggly lines that silently judge your work?&lt;/p&gt;

&lt;p&gt;There's a much cleaner, more professional solution, and it's been hiding in plain sight: the humble underscore &lt;code&gt;_&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  A Quick Story
&lt;/h3&gt;

&lt;p&gt;I remember the exact moment this convention went from a "nice-to-have" to a "must-enforce" on my team. I was doing a code review on a new feature. A mid-level dev had implemented a component that was connected to a third-party library. The library's &lt;code&gt;withProvider&lt;/code&gt; HOC passed down a bunch of props - &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;isLoading&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;, and &lt;code&gt;refetch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our component only needed &lt;code&gt;data&lt;/code&gt; and &lt;code&gt;isLoading&lt;/code&gt;. The dev’s solution was to just destructure all of them and let the linter scream about &lt;code&gt;error&lt;/code&gt; and &lt;code&gt;refetch&lt;/code&gt; being unused.&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;// The code I was reviewing&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refetch&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...logic using data and isLoading&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PR description simply said, "Linter warnings are expected." That was a red flag. It wasn't just about messy code; it was about a mindset. It communicated a willingness to fight the tools instead of working with them.&lt;/p&gt;

&lt;p&gt;I left a simple comment: "Let's signal that &lt;code&gt;error&lt;/code&gt; and &lt;code&gt;refetch&lt;/code&gt; are intentionally unused. Prefix them with an underscore."&lt;/p&gt;

&lt;p&gt;The revised code came back minutes later:&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;// The clean, intentional version&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_refetch&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...logic using data and isLoading&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more warnings. No more excuses in the PR description. It was a small change that reflected a much bigger principle: we write code for humans first, machines second. It clearly communicated intent, and that’s the hallmark of a seasoned developer.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why the Underscore Is So Powerful
&lt;/h3&gt;

&lt;p&gt;Using an underscore prefix for unused variables isn't a hack. It's a widely accepted convention that signals professionalism and brings tangible benefits.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It Screams Intent:&lt;/strong&gt; An underscore tells every developer who reads your code, "I acknowledge this variable's existence, and I have deliberately chosen not to use it." It’s the difference between a potential bug (&lt;code&gt;user&lt;/code&gt; that was forgotten) and a clear choice (&lt;code&gt;_user&lt;/code&gt; which is intentionally ignored).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It Works &lt;em&gt;With&lt;/em&gt; Your Linter:&lt;/strong&gt; Instead of disabling the incredibly useful &lt;code&gt;@typescript-eslint/no-unused-vars&lt;/code&gt; rule, you configure it to respect this convention. You get the best of both worlds: you still catch &lt;em&gt;truly&lt;/em&gt; unused variables (like typos or leftovers from a refactor) while allowing the intentionally unused ones to pass.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It's a Cross-Ecosystem Language:&lt;/strong&gt; This pattern isn't unique to TypeScript. You'll see it in Python, C#, and many other languages. Adopting it makes your code more accessible and demonstrates a breadth of experience.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Putting It into Practice: Common Examples
&lt;/h3&gt;

&lt;p&gt;So, where can you use this simple trick? Pretty much anywhere an unused variable shows up.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. React Props and Hooks
&lt;/h4&gt;

&lt;p&gt;This is a daily occurrence in React development. A component might receive props it only passes down, or a custom hook might return a value you don't need in a specific instance.&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;// Example: A component that doesn't use all its props&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserProfileProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;userId&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="nl"&gt;name&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="c1"&gt;// This prop is required by the parent, but not used here&lt;/span&gt;
  &lt;span class="nl"&gt;onSelect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Don't do this:&lt;/span&gt;
&lt;span class="c1"&gt;// const UserProfile = (props: UserProfileProps) =&amp;gt; &amp;lt;h2&amp;gt;{props.name}&amp;lt;/h2&amp;gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Do this:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserProfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;_rest&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;UserProfileProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or when working with hooks like &lt;code&gt;useState&lt;/code&gt; where you only need the setter:&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSomeValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Callback Parameters
&lt;/h4&gt;

&lt;p&gt;The classic use case. You often need the &lt;code&gt;index&lt;/code&gt; but not the &lt;code&gt;item&lt;/code&gt;, or vice versa.&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;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&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;b&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;c&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// I only need the index here.&lt;/span&gt;
&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Index:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Object Destructuring
&lt;/h4&gt;

&lt;p&gt;Ever needed to pull just one or two properties from a large API response object? The underscore is perfect for signaling which fields you’re intentionally discarding.&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;usefulField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_unusedField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_anotherUnused&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;apiResponse&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="nx"&gt;usefulField&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The One-Time Setup: Configure ESLint
&lt;/h3&gt;

&lt;p&gt;To make this convention seamless, you need to tell ESLint about it. It's a simple, one-time change to your &lt;code&gt;.eslintrc&lt;/code&gt; file.&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;"rules"&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;"@typescript-eslint/no-unused-vars"&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="s2"&gt;"error"&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;"argsIgnorePattern"&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;"varsIgnorePattern"&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;"caughtErrorsIgnorePattern"&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="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="err"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;argsIgnorePattern: "^_"&lt;/code&gt;: Ignores function arguments that start with an underscore.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;varsIgnorePattern: "^_"&lt;/code&gt;: Does the same for all other variables.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;caughtErrorsIgnorePattern: "^_"&lt;/code&gt;: Ignores error variables in &lt;code&gt;try...catch&lt;/code&gt; blocks.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  A Small Habit with a Big Impact
&lt;/h3&gt;

&lt;p&gt;Adopting the underscore prefix is one of those small disciplines that separates good code from great code. It demonstrates foresight, cleanliness, and a respect for your teammates who will read and maintain your work. It silences unnecessary noise, signals your intent clearly, and keeps your entire codebase looking sharp and professional.&lt;/p&gt;

&lt;p&gt;The next time you see that &lt;code&gt;no-unused-vars&lt;/code&gt; warning, don't write an excuse. Just add an underscore.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>My Pinch of Salt: Did AI and LLMs Kill the "First-Time Correct" Engineer?</title>
      <dc:creator>Vatsal Trivedi</dc:creator>
      <pubDate>Wed, 24 Sep 2025 16:41:32 +0000</pubDate>
      <link>https://dev.to/trivedivatsal/my-pinch-of-salt-did-ai-and-llms-kill-the-first-time-correct-engineer-3p9f</link>
      <guid>https://dev.to/trivedivatsal/my-pinch-of-salt-did-ai-and-llms-kill-the-first-time-correct-engineer-3p9f</guid>
      <description>&lt;p&gt;I remember a time, not too long ago, when the goal was to be &lt;strong&gt;First-Time Correct&lt;/strong&gt;. It was a badge of honor. It meant you spent hours whiteboarding, meticulously planning your logic, and debating data structures &lt;em&gt;before&lt;/em&gt; a single line of code was written. Committing something broken to the main branch was a walk of shame. The aim was craftsmanship.&lt;/p&gt;

&lt;p&gt;Today, something feels different. We're all about &lt;strong&gt;velocity&lt;/strong&gt;. The pressure is to push, to merge, to keep the CI/CD pipeline green and buzzing. And our new AI coding buddies—our Copilots and LLM assistants—are more than happy to help.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Chatbot Workflow
&lt;/h2&gt;

&lt;p&gt;Think about how we interact with a tool like ChatGPT. We ask a question. It gives an answer. It's not quite right. We tweak the prompt. Ask again. Still not there. We rephrase, add context, and ask a third time. &lt;em&gt;Voilà&lt;/em&gt;, the perfect solution.&lt;/p&gt;

&lt;p&gt;Now, look at our Git history. Does it look familiar?&lt;/p&gt;

&lt;p&gt;A commit is pushed. The build fails. A quick fix is pushed. It passes tests but breaks something downstream. Another commit is pushed. This cycle continues, a rapid-fire conversation with the codebase, until we stumble upon the correct solution. We're essentially mimicking our chatbot interactions in our development process. The feedback loop of &lt;em&gt;writing code&lt;/em&gt; is instantaneous, but the feedback loop of &lt;em&gt;shipping working software&lt;/em&gt; is getting bigger, messier, and riddled with the ghosts of failed attempts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Redefining "Versions"
&lt;/h2&gt;

&lt;p&gt;Some argue that we can never be "First-Time Correct," and I agree. Perfection is a myth. But we used to have a system for imperfection. We had &lt;strong&gt;v1&lt;/strong&gt;, &lt;strong&gt;v2&lt;/strong&gt;, and so on. These were deliberate releases, accompanied by clear notes on what was working, what was failing, and what was intentionally skipped. It was a structured approach to iteration.&lt;/p&gt;

&lt;p&gt;Now, our versioning feels… different. It's becoming:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;v1&lt;/strong&gt;: The code the AI generated in five seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v1.1 through v1.9&lt;/strong&gt;: The flurry of commits I had to make myself to fix all the things the AI missed, misunderstood, or hallucinated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our measure of success is subtly shifting from "Is it correct?" to "How fast did the AI help me fix the things it got wrong?" We've traded the discipline of forethought for the convenience of AI-powered hindsight. We're becoming expert editors of machine-generated code rather than architects of robust systems.&lt;/p&gt;

&lt;p&gt;We gained speed, no doubt. But in our race for velocity, are we forgetting the art of being right? 🤔&lt;/p&gt;




&lt;p&gt;So here's my open-ended question for you: &lt;strong&gt;Are we trading the enduring satisfaction of craftsmanship for the fleeting high of a passing pipeline?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>coding</category>
      <category>llm</category>
    </item>
    <item>
      <title>Lets Be Real: Its Time to Ditch `any` for `unknown` in TypeScript</title>
      <dc:creator>Vatsal Trivedi</dc:creator>
      <pubDate>Mon, 22 Sep 2025 11:22:57 +0000</pubDate>
      <link>https://dev.to/trivedivatsal/lets-be-real-its-time-to-ditch-any-for-unknown-in-typescript-2cn4</link>
      <guid>https://dev.to/trivedivatsal/lets-be-real-its-time-to-ditch-any-for-unknown-in-typescript-2cn4</guid>
      <description>&lt;p&gt;We’ve all seen it in a pull request. A developer hits a snag with a tricky data type, and to get things working, they reach for the easiest tool available: &lt;code&gt;any&lt;/code&gt;. It gets the job done and silences the compiler, but it comes at a hidden cost.&lt;/p&gt;

&lt;p&gt;Our team has a Husky pre-commit hook set up to flag &lt;code&gt;any&lt;/code&gt;, which is a great first step. But we all know that in a pinch, the &lt;code&gt;--no-verify&lt;/code&gt; flag is an easy out. This makes the code review our most important line of defense. When you spot an &lt;code&gt;any&lt;/code&gt; that has slipped through, it's the perfect opportunity to advocate for its safer, smarter alternative: &lt;code&gt;unknown&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Danger of &lt;code&gt;any&lt;/code&gt;: A "Trust Me" Promise to the Compiler 🙈
&lt;/h3&gt;

&lt;p&gt;Let’s be blunt: using &lt;code&gt;any&lt;/code&gt; is like telling the TypeScript compiler to just look the other way. When you type a variable as &lt;code&gt;any&lt;/code&gt;, you're effectively saying, "Disable all type-checking for this. I know what I’m doing."&lt;/p&gt;

&lt;p&gt;This means you can do literally anything with that variable, and TypeScript won't stop you until it explodes at runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's a classic 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;let&lt;/span&gt; &lt;span class="nx"&gt;myData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;myData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is a string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// TypeScript has no problem with this line...&lt;/span&gt;
&lt;span class="c1"&gt;// but it will crash your app!&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;myData&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="c1"&gt;// 😱 Uncaught TypeError: myData.toFixed is not a function&lt;/span&gt;

&lt;span class="c1"&gt;// The compiler is also perfectly happy with these obvious errors:&lt;/span&gt;
&lt;span class="nx"&gt;myData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;someMethodThatDoesNotExist&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;myData&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every &lt;code&gt;any&lt;/code&gt; you use punches a hole in the type safety we rely on TypeScript for. It turns potential compile-time catches into frustrating runtime bugs and makes refactoring a dangerous guessing game.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Savior: &lt;code&gt;unknown&lt;/code&gt; to the Rescue! 🦸
&lt;/h3&gt;

&lt;p&gt;This is where &lt;code&gt;unknown&lt;/code&gt; comes in and saves the day. Just like &lt;code&gt;any&lt;/code&gt;, you can assign any value—a string, a number, an object—to a variable typed as &lt;code&gt;unknown&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So what’s the big deal? The crucial difference is that &lt;code&gt;unknown&lt;/code&gt; won't let you &lt;em&gt;do anything&lt;/em&gt; with the value until you first prove what it is. You are forced to handle the uncertainty before you can use it.&lt;/p&gt;

&lt;p&gt;Think of an &lt;code&gt;unknown&lt;/code&gt; variable like a locked box. You know something’s inside, but you have to check what it is before you can safely use it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let's fix the previous example with &lt;code&gt;unknown&lt;/code&gt;:&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;let&lt;/span&gt; &lt;span class="nx"&gt;myData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;myData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is a string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// The compiler immediately stops you. This is a good thing!&lt;/span&gt;
&lt;span class="c1"&gt;// ❌ Error: Object is of type 'unknown'.&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;myData&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="c1"&gt;// Here's how you work with it safely:&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;myData&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// OK, inside this block, TypeScript now knows myData is a 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;myData&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="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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;myData&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;// And in here, it knows it's a 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;myData&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By switching to &lt;code&gt;unknown&lt;/code&gt;, you’re prompted to write more defensive, robust code. It makes you handle different cases explicitly with type guards like &lt;code&gt;typeof&lt;/code&gt; or &lt;code&gt;instanceof&lt;/code&gt;, leading to a much more stable application.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Code Reviewer's Playbook
&lt;/h3&gt;

&lt;p&gt;Spotting an &lt;code&gt;any&lt;/code&gt; during a code review is a great teaching moment. Here’s how to approach it constructively:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Understand the "Why":&lt;/strong&gt; The developer was likely just trying to solve a typing problem quickly. No need to be critical.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Explain the Risk:&lt;/strong&gt; Briefly mention that &lt;code&gt;any&lt;/code&gt; bypasses type safety and can hide bugs that will only show up at runtime.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Offer the Solution:&lt;/strong&gt; Suggest replacing &lt;code&gt;any&lt;/code&gt; with &lt;code&gt;unknown&lt;/code&gt;. Then, guide them to add the necessary type check (e.g., an &lt;code&gt;if&lt;/code&gt; block) to safely access the variable. This not only resolves the immediate risk but also makes the code's intent clearer for everyone.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  The Bottom Line
&lt;/h3&gt;

&lt;p&gt;While &lt;code&gt;any&lt;/code&gt; is a tempting shortcut, it undermines the very reason we use TypeScript. &lt;strong&gt;&lt;code&gt;unknown&lt;/code&gt; gives you the same flexibility to accept any type of value but without sacrificing type safety.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By encouraging the use of &lt;code&gt;unknown&lt;/code&gt; in our code reviews, we're fostering a habit of writing more deliberate, predictable, and robust code. It forces us to confront uncertainty head-on, resulting in fewer bugs and a healthier codebase. So next time you're tempted to use &lt;code&gt;any&lt;/code&gt;, pause and reach for &lt;code&gt;unknown&lt;/code&gt; instead. Your future self—and your teammates—will be grateful.&lt;/p&gt;




</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>codereview</category>
    </item>
  </channel>
</rss>
