<?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: KIRUTHIKA P G</title>
    <description>The latest articles on DEV Community by KIRUTHIKA P G (@kiruthika_pg).</description>
    <link>https://dev.to/kiruthika_pg</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4112319%2F05ccf0ff-a75b-4215-9d1e-7491f3fec78f.png</url>
      <title>DEV Community: KIRUTHIKA P G</title>
      <link>https://dev.to/kiruthika_pg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kiruthika_pg"/>
    <language>en</language>
    <item>
      <title>I Didn't Install dotenv: What Happened When We Rebuilt It From Scratch</title>
      <dc:creator>KIRUTHIKA P G</dc:creator>
      <pubDate>Sun, 06 Sep 2026 12:56:18 +0000</pubDate>
      <link>https://dev.to/kiruthika_pg/i-didnt-install-dotenv-what-happened-when-we-rebuilt-it-from-scratch-kd</link>
      <guid>https://dev.to/kiruthika_pg/i-didnt-install-dotenv-what-happened-when-we-rebuilt-it-from-scratch-kd</guid>
      <description>&lt;h1&gt;
  
  
  I Didn't Install dotenv: What Happened When We Rebuilt It From Scratch
&lt;/h1&gt;

&lt;p&gt;Every Node.js developer has probably done this:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Need validation?&lt;/p&gt;

&lt;p&gt;Install another package.&lt;/p&gt;

&lt;p&gt;Need environment variable expansion?&lt;/p&gt;

&lt;p&gt;Install another package.&lt;/p&gt;

&lt;p&gt;It works. It's convenient. And most of the time, we never stop to think about what those packages are actually doing underneath.&lt;/p&gt;

&lt;p&gt;For Zero Dependency 2026, we decided to ask a different question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much of this could we build ourselves using only Node.js?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So we built &lt;strong&gt;envkit&lt;/strong&gt; — a zero-dependency environment configuration and validation tool using only Node.js built-in APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;A typical Node.js project might use packages for several parts of environment configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading &lt;code&gt;.env&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;Expanding variables such as &lt;code&gt;${HOST}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Validating required configuration&lt;/li&gt;
&lt;li&gt;Providing useful command-line feedback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For our project, none of those external runtime packages were allowed.&lt;/p&gt;

&lt;p&gt;That meant we couldn't simply install the solution.&lt;/p&gt;

&lt;p&gt;We had to understand what the solution actually does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we decided to build
&lt;/h2&gt;

&lt;p&gt;Our goal was to create a small command-line tool that could:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.env
  ↓
Parse
  ↓
Expand variables
  ↓
Validate
  ↓
Run the application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main commands are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;envkit check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;envkit run &lt;span class="nt"&gt;--&lt;/span&gt; node app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first checks whether the environment configuration is valid.&lt;/p&gt;

&lt;p&gt;The second validates the environment before starting the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rebuilding the part I normally get from dotenv
&lt;/h2&gt;

&lt;p&gt;Our first challenge was parsing &lt;code&gt;.env&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A simple file looks easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PORT=3000
HOST=localhost
API_KEY=abc123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading the file itself wasn't difficult. Node's standard library already provides the filesystem functionality we needed.&lt;/p&gt;

&lt;p&gt;The interesting part was everything around it.&lt;/p&gt;

&lt;p&gt;We had to decide how our parser would handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blank lines&lt;/li&gt;
&lt;li&gt;Comments&lt;/li&gt;
&lt;li&gt;Whitespace&lt;/li&gt;
&lt;li&gt;Quoted values&lt;/li&gt;
&lt;li&gt;Escaped characters&lt;/li&gt;
&lt;li&gt;Invalid lines&lt;/li&gt;
&lt;li&gt;Multiline values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lesson was simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reading a file is easy. Correctly interpreting a configuration format is where the complexity begins.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of importing a parser, we implemented that logic ourselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then variable expansion looked easy
&lt;/h2&gt;

&lt;p&gt;We wanted to support configuration such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HOST=localhost
PORT=3000
API_URL=http://${HOST}:${PORT}/api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API_URL=http://localhost:3000/api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first, this looked like a simple string replacement problem.&lt;/p&gt;

&lt;p&gt;Then we considered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A=${B}
B=${A}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A naive implementation can keep resolving the variables forever.&lt;/p&gt;

&lt;p&gt;So we needed to track which variables were currently being resolved.&lt;/p&gt;

&lt;p&gt;Instead of hanging, envkit can report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Circular reference detected: A → B → A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was one of the moments where rebuilding a familiar feature became much more interesting than simply using a package.&lt;/p&gt;

&lt;p&gt;A feature that looked like "replace &lt;code&gt;${VARIABLE}&lt;/code&gt;" suddenly involved dependency resolution and cycle detection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rebuilding validation
&lt;/h2&gt;

&lt;p&gt;The next part was environment validation.&lt;/p&gt;

&lt;p&gt;We wanted developers to be able to describe requirements such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="nx"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;:&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="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="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="nx"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&gt;:&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="s2"&gt;enum&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;development&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="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a configuration such as:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;should produce something useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✗ PORT must be a number
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of installing a validation library, we implemented the checks using JavaScript's built-in language features.&lt;/p&gt;

&lt;p&gt;The surprising part wasn't checking a type.&lt;/p&gt;

&lt;p&gt;The difficult part was designing &lt;strong&gt;good error messages&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A validator that simply says "invalid" isn't very helpful.&lt;/p&gt;

&lt;p&gt;We wanted the developer to immediately understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which variable failed&lt;/li&gt;
&lt;li&gt;Why it failed&lt;/li&gt;
&lt;li&gt;What was expected&lt;/li&gt;
&lt;li&gt;Whether multiple variables were incorrect&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The CLI
&lt;/h2&gt;

&lt;p&gt;Once the individual pieces worked, we connected them through a command-line interface.&lt;/p&gt;

&lt;p&gt;The intended workflow became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;envkit check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which performs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read .env
   ↓
Parse
   ↓
Expand
   ↓
Validate
   ↓
Show result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;envkit run &lt;span class="nt"&gt;--&lt;/span&gt; node app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;performs the same validation before starting the application.&lt;/p&gt;

&lt;p&gt;If validation fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✗ Environment validation failed.

Application was not started.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it succeeds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Environment valid
✓ Starting application...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That turned the project from a collection of small utilities into an actual developer tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question of trust
&lt;/h2&gt;

&lt;p&gt;One of the questions we kept coming back to was:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why should someone trust our implementation instead of the packages we replaced?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer isn't "because we're better."&lt;/p&gt;

&lt;p&gt;That would just replace one form of blind trust with another.&lt;/p&gt;

&lt;p&gt;The more interesting idea is &lt;strong&gt;verifiable trust&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A developer can inspect our relatively small codebase and understand what it does.&lt;/p&gt;

&lt;p&gt;There isn't a large dependency tree hiding behind the application.&lt;/p&gt;

&lt;p&gt;There is no external runtime package that can silently become part of the application because another package pulled it in.&lt;/p&gt;

&lt;p&gt;Our goal isn't to eliminate trust.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's to make the trusted surface small enough to inspect.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What we learned
&lt;/h2&gt;

&lt;p&gt;The biggest lesson wasn't that npm packages are bad.&lt;/p&gt;

&lt;p&gt;They aren't.&lt;/p&gt;

&lt;p&gt;Mature packages exist because developers have already spent years discovering and fixing edge cases.&lt;/p&gt;

&lt;p&gt;Our experiment showed us why those abstractions are useful.&lt;/p&gt;

&lt;p&gt;When you remove the package, you suddenly encounter the complexity it was hiding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotenv
   ↓
Parsing rules

dotenv-expand
   ↓
Variable resolution
   ↓
Circular references

Validation library
   ↓
Type checking
   ↓
Defaults
   ↓
Error reporting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The standard library gave us the primitives.&lt;/p&gt;

&lt;p&gt;But the engineering decisions were ours.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we don't claim
&lt;/h2&gt;

&lt;p&gt;envkit is not intended to pretend that a few hundred lines of code can instantly replace years of work in mature production libraries.&lt;/p&gt;

&lt;p&gt;We will document the features we support and the edge cases we intentionally don't support.&lt;/p&gt;

&lt;p&gt;That honesty is part of the project.&lt;/p&gt;

&lt;p&gt;A zero-dependency project shouldn't hide its limitations just to look impressive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proving the dependency claim
&lt;/h2&gt;

&lt;p&gt;The final project should make the zero-dependency requirement easy to verify.&lt;/p&gt;

&lt;p&gt;Our Node.js project has no third-party runtime dependencies.&lt;/p&gt;

&lt;p&gt;Instead of asking a judge to trust our claim, we provide dependency proof and document the standard-library substitutions in &lt;code&gt;STDLIB.md&lt;/code&gt;.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Package we would normally use → What we used instead

dotenv        → Node filesystem + our parser
dotenv-expand → Our variable resolver
zod/envalid   → Our validation engine
chalk         → ANSI terminal escape sequences
Jest          → node:test + node:assert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point isn't simply to have an empty &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The point is to explain what we built instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Zero Dependency forced us to slow down and look underneath abstractions we normally take for granted.&lt;/p&gt;

&lt;p&gt;We started with:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;and ended up asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What does dotenv actually have to do?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then we asked the same question about expansion, validation, and CLI tooling.&lt;/p&gt;

&lt;p&gt;The result is envkit: a small Node.js developer tool built without third-party runtime dependencies.&lt;/p&gt;

&lt;p&gt;The most valuable part wasn't removing the packages.&lt;/p&gt;

&lt;p&gt;It was discovering &lt;strong&gt;why those packages exist in the first place.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
