<?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: janardhan reddy</title>
    <description>The latest articles on DEV Community by janardhan reddy (@janardhan_reddy_8ae708fe0).</description>
    <link>https://dev.to/janardhan_reddy_8ae708fe0</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%2F1731463%2F5b50c2be-2241-4cf7-97e1-93d8bac16faf.png</url>
      <title>DEV Community: janardhan reddy</title>
      <link>https://dev.to/janardhan_reddy_8ae708fe0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/janardhan_reddy_8ae708fe0"/>
    <language>en</language>
    <item>
      <title># JavaScript Finally Got Destructors, Sort Of, and Nobody Told Me For A Year</title>
      <dc:creator>janardhan reddy</dc:creator>
      <pubDate>Tue, 04 Aug 2026 20:18:50 +0000</pubDate>
      <link>https://dev.to/janardhan_reddy_8ae708fe0/-javascript-finally-got-destructors-sort-of-and-nobody-told-me-for-a-year-562o</link>
      <guid>https://dev.to/janardhan_reddy_8ae708fe0/-javascript-finally-got-destructors-sort-of-and-nobody-told-me-for-a-year-562o</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd6x2d1bpqdpb3bs0693m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd6x2d1bpqdpb3bs0693m.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Grafana alert said "connection pool: 100/100 in use." Nothing dramatic, no outage, just a slow climb over three days until a batch job started timing out waiting for a connection that was never coming. The culprit, once I actually went looking, was a helper function with an early return inside a try block, sitting above the &lt;code&gt;finally&lt;/code&gt; that closed the connection. Somebody added that return six months earlier to handle an edge case, tested it, shipped it, and never noticed the &lt;code&gt;finally&lt;/code&gt; two lines down had quietly stopped mattering for that one code path.&lt;/p&gt;

&lt;p&gt;That bug is old as JavaScript itself. &lt;code&gt;try/finally&lt;/code&gt; works fine until it doesn't, and it doesn't the moment your cleanup logic depends on every exit path being wired correctly by hand, forever, across every future edit. ES2026, ratified by ECMA International on June 30 this year, finally gives us a language-level answer: explicit resource management, via the &lt;code&gt;using&lt;/code&gt; and &lt;code&gt;await using&lt;/code&gt; declarations.&lt;/p&gt;

&lt;p&gt;I'd seen the proposal floating around for a couple of years and mentally filed it under "neat, probably won't ship for a while." It shipped. Here's what it actually does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem It's Solving
&lt;/h2&gt;

&lt;p&gt;Anything that needs cleanup, a database connection, a file handle, a lock, an event listener, a timer, has the same shape of risk. You open it, you use it, and somewhere along the way you need to close it, no matter how the function exits: normal return, early return, thrown error, whatever. &lt;code&gt;try/finally&lt;/code&gt; is the tool for that today, and it works, but it's manual. Nest a few of these and the boilerplate multiplies:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;openFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO logs VALUES ($1)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every resource adds a nesting level. Every nesting level is a chance to close things in the wrong order, or forget one entirely when you're refactoring at 90 miles an hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  What using Actually Does
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;using&lt;/code&gt; declaration ties a value's lifetime to the scope it's declared in. When that scope exits, for any reason, the runtime automatically calls a disposal method on the value. No &lt;code&gt;finally&lt;/code&gt; required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DbConnection&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&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;connection released&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DbConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// conn[Symbol.dispose]() runs automatically here,&lt;/span&gt;
  &lt;span class="c1"&gt;// even if an error was thrown above&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Symbol.dispose&lt;/code&gt; is the new well-known symbol that makes an object "disposable." Anything implementing it can be declared with &lt;code&gt;using&lt;/code&gt;, and the engine guarantees the dispose method runs when the block ends, whether it ends by falling through, returning, or throwing.&lt;/p&gt;

&lt;p&gt;For anything where cleanup itself is asynchronous (closing a database pool that needs to flush, releasing a WebSocket that sends a close frame), there's &lt;code&gt;await using&lt;/code&gt;, paired with &lt;code&gt;Symbol.asyncDispose&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FileHandle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asyncDispose&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;openFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// await file[Symbol.asyncDispose]() is awaited before this function&lt;/span&gt;
  &lt;span class="c1"&gt;// actually resolves&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rewriting the earlier nested example with both:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;openFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DbConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO logs VALUES ($1)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&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;Two flat declarations instead of two nested try blocks. Disposal still happens in the right order too, reverse of declaration, so &lt;code&gt;conn&lt;/code&gt; gets released before &lt;code&gt;file&lt;/code&gt; gets closed, matching what the nested version did by hand.&lt;/p&gt;

&lt;p&gt;One detail that trips people up the first time: &lt;code&gt;using&lt;/code&gt; isn't a function call, it's a declaration keyword, sitting where &lt;code&gt;const&lt;/code&gt; or &lt;code&gt;let&lt;/code&gt; would go. The value on the right side of the assignment still has to actually implement &lt;code&gt;Symbol.dispose&lt;/code&gt; or &lt;code&gt;Symbol.asyncDispose&lt;/code&gt;. If it doesn't, you get a &lt;code&gt;TypeError&lt;/code&gt; at the point of disposal, not at declaration, which is a slightly annoying place to discover a typo but at least it's loud about it. There's no ambiguity about ownership either. The variable declared with &lt;code&gt;using&lt;/code&gt; owns the resource for the lifetime of that block, full stop, and nothing else needs to remember to release it.&lt;/p&gt;

&lt;h2&gt;
  
  
  DisposableStack, For When You Don't Know Up Front
&lt;/h2&gt;

&lt;p&gt;Sometimes you're building up a set of resources conditionally and don't know at declaration time exactly what needs cleaning up. &lt;code&gt;DisposableStack&lt;/code&gt; and its async counterpart exist for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setupResources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DisposableStack&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;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DbConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;needsCache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CacheConnection&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// hand off ownership, cleanup deferred&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything pushed onto the stack gets disposed in reverse order when the stack itself is disposed, or you can call &lt;code&gt;.move()&lt;/code&gt; to transfer ownership somewhere else without triggering cleanup yet. It's a small thing, but if you've ever written a setup function that had to manually track "which of these three things did I actually open before this one failed," you'll recognize the itch it's scratching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Actually Matters In A React/Next.js Codebase
&lt;/h2&gt;

&lt;p&gt;Most day-to-day component code won't touch this directly, React's own cleanup model (the return function from &lt;code&gt;useEffect&lt;/code&gt;) already handles the common case. Where &lt;code&gt;using&lt;/code&gt; earns its keep is lower down: database clients in route handlers, file system operations in build scripts, worker threads, anything talking to Node's &lt;code&gt;fs&lt;/code&gt;, &lt;code&gt;net&lt;/code&gt;, or a database driver that exposes disposal hooks. A few libraries have started shipping &lt;code&gt;Symbol.dispose&lt;/code&gt; support on their client objects already, and more will as this settles in. If you maintain an internal wrapper around a connection pool or a temp file utility, that's a reasonable place to add &lt;code&gt;[Symbol.dispose]&lt;/code&gt; now and get the syntax for free later.&lt;/p&gt;

&lt;p&gt;Browser support and Node support both landed before the June ratification (engines had been shipping it behind flags for a while, since TC39 proposals at Stage 3 are considered stable enough to implement early), so this isn't a "wait three years" situation. Check your target runtime version, but the practical answer for most Next.js apps on recent Node is: it already works.&lt;/p&gt;

&lt;p&gt;One catch if your team runs a shared TypeScript config: you'll need a recent &lt;code&gt;target&lt;/code&gt; and &lt;code&gt;lib&lt;/code&gt; setting for the compiler to recognize &lt;code&gt;using&lt;/code&gt; and the two new well-known symbols. Older tsconfig presets copied from a project started a couple of years back won't know what to do with the syntax and will just error out, which looks scarier than it is. Bump the lib target, and it resolves itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  So Was It Worth The Wait
&lt;/h2&gt;

&lt;p&gt;Kind of. It doesn't fix bugs that already shipped, my connection pool leak from three paragraphs ago is exactly as leaked whether or not &lt;code&gt;using&lt;/code&gt; exists, because it was written before this landed and nobody's going back to rewrite working code just because a nicer syntax showed up. What it does is remove the specific failure mode where correctness depends on a human remembering to nest try/finally correctly across every edit forever. That's a real thing to fix. It's also, in the end, syntax sugar over a pattern good engineers already enforced by hand and via linters.&lt;/p&gt;

&lt;p&gt;I added &lt;code&gt;Symbol.dispose&lt;/code&gt; to our internal db client wrapper this week. It shaved about four lines off each call site. Not a revolution. Just fewer chances to leave a connection open at 3 AM.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>web</category>
      <category>node</category>
    </item>
    <item>
      <title>Your Middleware Is Not a Security Boundary (59,000 Servers Just Found That Out)</title>
      <dc:creator>janardhan reddy</dc:creator>
      <pubDate>Tue, 04 Aug 2026 20:05:32 +0000</pubDate>
      <link>https://dev.to/janardhan_reddy_8ae708fe0/your-middleware-is-not-a-security-boundary-59000-servers-just-found-that-out-3ao0</link>
      <guid>https://dev.to/janardhan_reddy_8ae708fe0/your-middleware-is-not-a-security-boundary-59000-servers-just-found-that-out-3ao0</guid>
      <description>&lt;p&gt;3:47 AM, and a pager alert for unusual traffic on &lt;code&gt;/api/admin/*&lt;/code&gt;. Routes that should have been sealed off behind &lt;code&gt;middleware.ts&lt;/code&gt;, returning a clean 401 to anyone without a session cookie. Except the logs showed 200s. Hundreds of them, from IPs that had never touched the app before, all hitting the same handful of admin endpoints within a ten minute window.&lt;/p&gt;

&lt;p&gt;That's roughly how a lot of security teams spent a night in early December 2025, when a campaign researchers later named Operation PCPcat started chewing through Next.js deployments. In under 48 hours it compromised more than 59,000 servers, stealing somewhere between 300,000 and 590,000 credential sets out of environment files, SSH keys, and cloud service tokens. A success rate over 64 percent. Each infected box scanning for new targets every 45 minutes, which is the kind of detail that makes you close your laptop and stare at the wall for a minute.&lt;/p&gt;

&lt;p&gt;The vulnerability at the center of it, CVE-2025-29927, wasn't exotic. It came down to a single internal header, &lt;code&gt;x-middleware-subrequest&lt;/code&gt;, that Next.js used to avoid infinite loops when middleware triggers its own rewrites. The framework trusted that header completely. It never checked whether the header actually came from Next.js itself or from a random curl command on the internet. Send the right value, and the runtime would skip your entire middleware chain, auth checks included, like they were never there.&lt;/p&gt;

&lt;p&gt;If you're the kind of developer who put all your access control logic in &lt;code&gt;middleware.ts&lt;/code&gt; because it felt clean and centralized (and, hand up, that used to be me), this is the part that stings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Middleware-Only Auth Was Always A Bit Fragile
&lt;/h2&gt;

&lt;p&gt;Middleware in Next.js runs at the edge, before a request ever reaches your route handler or page. That's exactly why it's tempting as an auth gate: one file, one place to check the session, and every downstream route inherits the protection. It looks like a security boundary. It behaves like a security boundary, most of the time.&lt;/p&gt;

&lt;p&gt;But middleware is still application code, executing inside the same runtime as everything else, reachable by the same request that everything else sees. It isn't a firewall rule sitting outside your app's blast radius. It's a function that runs early. And any function that runs early can, in principle, be convinced not to run at all, whether through a header trust issue like this one, a misconfigured matcher pattern, or just a route added later that nobody remembered to protect.&lt;/p&gt;

&lt;p&gt;The patched versions (12.3.5, 13.5.9, 14.2.25, 15.2.3, and later) fixed the header trust problem specifically. Good. Update your dependencies, obviously. But patching one bug in the gate doesn't change the fact that you built a single gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Held Up
&lt;/h2&gt;

&lt;p&gt;The apps that shrugged this off weren't the ones with the fanciest middleware. They were the ones that treated middleware as a UX optimization (redirect unauthenticated users before they waste a full render) and kept the real authorization check inside the code path that actually touches sensitive data.&lt;/p&gt;

&lt;p&gt;Something like this, in a route handler:&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;// app/api/admin/users/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getSession&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unauthorized&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&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;That check runs regardless of whether middleware executed, got bypassed, got skipped by a header nobody asked for, or simply wasn't matched because of a routing quirk. It's redundant with the middleware check, on purpose. Redundant is the point. Defense in depth is a cliche because it keeps being true, not because anyone enjoys writing the same &lt;code&gt;if&lt;/code&gt; statement twice.&lt;/p&gt;

&lt;p&gt;None of this is theoretical hand waving about "best practices," by the way. The teams that got hit hardest by PCPcat were, almost without exception, running the exact self-hosted, standalone-output configuration this CVE targets, with nothing checking authorization below the middleware layer. Once the header trick worked, the request landed directly on a route handler that assumed, wrongly, that it would never be reached by anyone who hadn't already cleared the gate upstream. The handler itself had no opinion about who was asking. That's the actual failure, not "someone forgot to patch," though that mattered too. The architecture had exactly one checkpoint, and the checkpoint had exactly one weakness.&lt;/p&gt;

&lt;p&gt;Same idea applies one layer down, at the data access function itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUsersForAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Forbidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&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;Now even if some new route handler forgets to check the session (it happens, someone's always in a hurry to ship a dashboard widget), the data layer still refuses to hand out rows it shouldn't. You end up with three places doing the "are you allowed to see this" check: middleware for the fast redirect, the handler for the request-level gate, and the query function for the last line of defense. Slower to write. Much harder to accidentally leave a hole in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Part Where I Admit The Old Way Was Convenient
&lt;/h2&gt;

&lt;p&gt;I get why middleware-only auth spread. It's less code. Fewer places to remember to add a check when you're cranking out a new admin route at 6 PM on a Thursday because product wants it demoed Friday morning. Centralizing the logic in one file felt like good engineering, and honestly it read well in code review. "Look, one middleware function, protects everything under &lt;code&gt;/admin&lt;/code&gt;." Nobody asks the follow-up question of what happens if that one function gets bypassed, because until December 2025 that wasn't really a live scenario most teams had internalized.&lt;/p&gt;

&lt;p&gt;It is now. The attackers behind PCPcat weren't picking apart bespoke logic, they were running the exact same header trick against every vulnerable Next.js app they could find, automatically, at scale. If your only defense was "middleware checks the cookie," and middleware could be told to sit this one out, there was nothing behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Short List Of Things Worth Checking This Week
&lt;/h2&gt;

&lt;p&gt;If you're running self-hosted Next.js with &lt;code&gt;output: standalone&lt;/code&gt; (the Vercel-hosted deployments were not affected by this particular CVE, for what it's worth), confirm you're on a patched minor version. Then go look at whatever routes matter most, the ones touching PII, admin actions, billing, and ask whether the authorization check would still fire if middleware silently didn't run. If the honest answer is "no, it's middleware or nothing," that's worth an afternoon of refactoring before it's worth an incident review.&lt;/p&gt;

&lt;p&gt;None of this is a knock on middleware as a tool. It's genuinely great for redirects, locale detection, feature flag routing, all the stuff that's annoying without it. It's just not a wall. It's a hallway monitor. Useful, but you still want a lock on the actual door.&lt;/p&gt;

&lt;p&gt;There's also a quieter lesson buried in here about how we talk about architecture in code review. "Centralized" got treated as a synonym for "secure" for a long time, and those aren't the same property. A single checkpoint is easier to reason about, easier to test, easier to point to in a design doc. It is also, definitionally, a single point of failure. Sometimes the right tradeoff really is one central gate, especially for low-stakes routes. For anything touching money, PII, or admin capability at a payments company, the tradeoff isn't close. You want the boring, repetitive, slightly annoying version where three layers each independently refuse to hand over data to someone who shouldn't have it.&lt;/p&gt;

&lt;p&gt;I patched our stuff, added the redundant checks, and went back to arguing about whether we needed another loading skeleton component. Turns out most weeks are still mostly that.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>typescript</category>
      <category>javascript</category>
      <category>react</category>
    </item>
  </channel>
</rss>
