LabHub

Blog

Deno 2 and 3 Deep Dive 2026 — The Node-Compatibility Era, JSR, Fresh 2, and Deno KV (Where the Runtime Bet Went) english

한국어English日本語

Prologue — Ryan Dahl's Second Self-Negation

Ryan Dahl built Node in 2009. In 2018 at JSConf EU, he gave the famous "10 Things I Regret About Node.js" talk. Then he built Deno. Default-deny security, TS first-class, URL imports, no package.json — every choice was an explicit answer to Node.

Then in September 2024, Deno 2 shipped. The biggest change is self-negation.

Deno accepted, six years later, almost every Node design decision it had initially rejected. Pride swallowed.

Is this a retreat or an evolution? Both. A shift from idealism to pragmatism, and at the same time, a bet that acknowledges the reality of the JS ecosystem (npm is not going away).

This essay summarizes where Deno's bet has landed about 20 months after the Deno 2 launch — mid-2026 — how the three-way battle with Bun and Node 22+ settled, and where the security model, JSR, Fresh 2, and Deno KV actually sit in the real world.


Chapter 1 · The Heart of Deno 2 — Node Compatibility Becomes the Default

The Deno 1 worldview, and its limits

Deno 1 tried to draw a pure world.

The problem was simple. 90 percent of the JS world already lives on npm. Express, React, Prisma, AWS SDK, Sentry, Stripe SDK — all npm. Deno added the npm: specifier early as a compat shim, but getting to real production fell short. The package.json already in the repo couldn't be read directly, full-stack tools that touched node_modules broke, monorepo workflows felt awkward.

Deno 2's answer was clear. "It also runs the way Node ran."

What changed (September 2024)

# Deno 2 — from an existing Node project
deno install            # reads package.json, creates node_modules
deno run npm:express    # runs an npm package directly
deno task dev           # runs scripts.dev from package.json
AreaDeno 1Deno 2
package.jsonignored / partialfirst-class — deno install reads it
node_modulesdeliberately absentoptional — nodeModulesDir: auto
npm packagesnpm: specifiersame, plus automatic from package.json
deno installURL-based binary installNode-compatible package manager
deno run npm:foopartialstable, broad compat
workspacenonefirst-class — workspace field in deno.json

Deno 2's motto is no longer "replace Node." It is "shrink the friction between Node and Deno to near zero."

Has migration become easy?

Trying to take an existing Node project and run it on Deno:

The friction-heavy area for the first year was native addons (sharp, canvas, bcrypt-native, anything with C++ bindings), and N-API compatibility improved steadily through 2025. By mid-2026, more than 90 percent of typical npm packages run on Deno 2 without issue.


Chapter 2 · deno install — The npm-Compatible Package Manager

Two modes

deno install runs in two modes.

# Global binary install (since Deno 1)
deno install -gA --name lint jsr:@std/cli/parser

# Project dependency install (new in Deno 2)
deno install            # reads package.json, creates node_modules
deno install --add npm:express@4   # add a new dependency
deno install --add jsr:@hono/hono  # add a JSR dependency

The second mode is the paradigm shift. It is now a command that can directly replace npm install, pnpm install, yarn install.

Speed and lockfile

hot cache, ~500 deps:
  npm install:    14s
  pnpm install:    4s
  bun install:     1.3s
  deno install:    2.1s

Not as fast as bun install, but faster than pnpm install and much faster than npm install. The lockfile is deno.lock (text JSON), and was diff-friendly from day one. A contrast with Bun's brief misstep on a binary lockfile.

node_modules mode

// deno.json
{
  "nodeModulesDir": "auto",   // or "manual" or false
  "lock": true
}

With auto, it reads package.json and creates node_modules automatically. With manual, it uses an existing node_modules populated by npm/pnpm/yarn (useful for large monorepo migrations). With false, only the Deno-1-style global cache is used.

The mere existence of this option is evidence that idealism was set aside in favor of pragmatism.


Chapter 3 · deno run for npm Packages Directly

Evolution of the npm: specifier

One of Deno 2's mottos: "import npm packages with no extra setup."

// app.ts
import express from "npm:express@4";
import { z } from "npm:zod";
import OpenAI from "npm:openai";

const app = express();
app.get("/", (_, res) => res.json({ ok: true }));
app.listen(3000);
deno run --allow-net --allow-read --allow-env app.ts

This just runs. No package.json needed, no node_modules needed (pulled into the global cache). Compared with Bun and Node which require package.json to start a project, single-file scripts are smoothest on Deno.

Compare with Bun and Node

# Single-file script with an npm dependency

# Deno
deno run --allow-net script.ts

# Bun
bun script.ts            # requires bun add first, or an inline shebang trick

# Node
npm init -y && npm i express && node script.js

In the quick-script-plus-one-or-two-npm-deps space, Deno still has the best dev UX. A Deno 1 strength that survived into Deno 2.


Chapter 4 · JSR — The JavaScript Registry Bet

Why another registry

JSR (jsr.io) is a new JavaScript registry the Deno team launched in early 2024. With npm already there, why another?

JSR's thesis:

  1. TS-native publishing — publish TS source directly. Consumers pick their own build tooling. (npm typically ships compiled JS plus .d.ts.)
  2. Standard ESM only — no CJS, none of the package.json maze.
  3. Runtime-agnostic — importable from Deno, Node, Bun, browsers.
  4. Quality score — a score showing documentation, tests, and type completeness.
  5. Scopes as first-class@scope/package as the model.

Usage examples

// just import
import { parse } from "jsr:@std/cli/parser";
import { Hono } from "jsr:@hono/hono";
# package.json style
deno install --add jsr:@hono/hono
# Also usable from Node
npx jsr add @hono/hono
# or directly — receives a built form

Adoption reality — mid-2026

CategoryJSR adoption
Deno first-party @std libraries100 percent — the new home for @std
Deno-ecosystem toolingbroad — Hono, Fresh, Oak, etc.
General JS librariesslow — many publish to npm in parallel
User basesmall slice of npm — but growing fast

JSR is not "kill npm" — it is "create a TS-first alternative." And it has partly succeeded: standard inside the Deno ecosystem, niche outside.

What JSR changed

The npm side's reaction

A curious detail. In 2024 Deno also produced a BSD-licensed fork of the npm CLI — not named "npm" but a different name (to honor community guidelines). It triggered a small license/naming debate, but there was no major legal response from npm Inc. The message ended up being "npm compatibility comes first, antagonism second."


Chapter 5 · Fresh 2 — Islands Architecture, Round Two

Fresh 1's promise

Fresh was Deno's official full-stack framework. Core ideas:

A great bet, but the 1.x era hit limits with the routing model (file-based plus handler separation) and the absence of React-ecosystem compatibility that constrained adoption.

What Fresh 2 changed

// routes/api/hello.ts (Fresh 2 style)
import { define } from "$fresh/server.ts";

export const handler = define.handlers({
  async GET(ctx) {
    return new Response(JSON.stringify({ msg: "hello" }), {
      headers: { "content-type": "application/json" },
    });
  },
});
// routes/index.tsx
import { define } from "$fresh/server.ts";
import Counter from "../islands/Counter.tsx";

export default define.page<{ now: string }>((props) => (
  <main>
    <h1>Current time: {props.data.now}</h1>
    <Counter start={0} />
  </main>
));

Notable changes in Fresh 2:

Why Fresh did not catch up in adoption (honestly)

Fresh 2 is a strong choice for full-stack tied to Deno Deploy. But its path to becoming the full-stack default is narrow.


Chapter 6 · Deno KV — The Built-In KV Goes Production-Ready

What Deno KV is

Deno KV is a key-value store built into the Deno runtime. A single-node mode similar to SQLite, plus globally distributed mode in the Deno Deploy environment.

// kv.ts
const kv = await Deno.openKv();

// set
await kv.set(["users", "alice"], { name: "Alice", joined: Date.now() });

// get
const result = await kv.get<{ name: string }>(["users", "alice"]);
console.log(result.value?.name);  // "Alice"

// list (prefix query)
for await (const entry of kv.list({ prefix: ["users"] })) {
  console.log(entry.key, entry.value);
}

// atomic transaction
await kv.atomic()
  .check({ key: ["counter"], versionstamp: null })
  .set(["counter"], 1)
  .commit();

Status in 2026

AspectStatus
Local (SQLite-backed)GA (production-ready)
Deno Deploy (FoundationDB-backed, global)GA
Cross-region replicationautomatic
Atomic transactionssupported
Queue APIsupported (kv.enqueue)
Watch APIsupported — real-time subscriptions
Limitsper-key 64KB value, some plan limits

Deno KV targeted the Cloudflare KV and Workers KV space, but since it is tied to the Deno ecosystem, positioning is tighter. Even so, the slogan "a distributed KV you can start with one line" actually holds up.

Where it fits well

Where it is weak


Chapter 7 · Workspace — Deno's Monorepo Answer

Monorepos become first-class

Deno 2 added the workspace field in deno.json.

// root deno.json
{
  "workspace": [
    "./packages/api",
    "./packages/web",
    "./packages/shared"
  ],
  "tasks": {
    "dev": "deno task --filter=* dev"
  }
}
// packages/api/deno.json
{
  "name": "@app/api",
  "version": "0.1.0",
  "exports": "./mod.ts",
  "tasks": {
    "dev": "deno run --watch --allow-net mod.ts"
  }
}

Internal packages import via import { foo } from "@app/shared". Nearly the same model as pnpm or Bun workspace, but install time is very short thanks to Deno's unified lockfile and cache.

Compared to Turborepo and Nx

ToolStrengthsWeaknesses
Deno workspacezero-config, lockfile-unified, fast installtask orchestration is simple
pnpm workspacebroad compat, familiar to everyoneinstall slower than Deno
Turborepobuild caching, strong graphconfiguration weight
Nxpowerful generators, big-monorepo standardsteep learning curve

Deno workspace shines in small to medium monorepos. Large monorepos where build-graph caching is essential still favor Turborepo or Nx.


Chapter 8 · Security Model — Still the Killer Feature

The differentiator that has survived from the very beginning. Default deny.

Permission flags at a glance

deno run script.ts                    # can do almost nothing
deno run --allow-read script.ts       # file reads
deno run --allow-net script.ts        # network
deno run --allow-env script.ts        # env vars
deno run --allow-write=./data script.ts   # write only this directory

# Deno 2 and later: finer scoping
deno run --allow-net=api.example.com,db.internal:5432 script.ts
deno run --allow-env=DATABASE_URL,REDIS_URL script.ts

What it really means in operations

Upsides:

Friction:

Deno 2 security improvements

The security model gained value in 2025 and 2026 as software supply-chain attacks rose. After the xz-utils incident and the npm event-stream incident, more companies took the "default deny" stance seriously.


Chapter 9 · Where Is Deno 3?

Status in mid-2026

Deno 3 is not yet officially released (as of May 2026). The roadmap and RFC discussions point in these directions.

Whether the next big bet shows up or whether it remains incremental is not yet decided. Since Deno 2 was a large self-negation, Deno 3 is more likely to be a conservative evolution.


Chapter 10 · Deno vs Bun vs Node 22+ — Settling the Three-Way Battle

Positions in mid-2026

AxisNode 22+BunDeno 2
Adoption1st (dominant)2nd (dev-tooling focus)3rd (specific niches)
Cold start35-60ms10-20ms25-40ms
Node compat100 percent (itself)90-95 percent90-95 percent (since Deno 2)
TS native--experimental-strip-typeszero-configzero-config
Security modelstandard (all allowed)standarddefault-deny
Std librarylarge coresmallrich @std
Package managernpm/pnpm/yarnbun installdeno install
Registrynpm onlynpm onlynpm plus JSR
Full-stack frameworkNext/Astro/Remix(whatever runs on Node)Fresh 2
Built-in KVnonebun:sqliteDeno KV
Edge deployVercel/NetlifyCloudflare Workers is separate isolateDeno Deploy
AI agent sandboxpossible (manual)possibleexcellent fit (permissions)

Decision guide

Where Deno 2 fits:

  1. AI agent sandbox — running model-generated code with bounded permissions. Deno is clearly first here.
  2. High-security / government / parts of finance — the permission model becomes meaningful in audit trails.
  3. Quick prototypes of new projects — single-file scripts plus npm dependencies are smoothest here.
  4. Small full-stack on Deno Deploy — Fresh 2 plus Deno KV.
  5. Backend tooling centered on @std — stability of the standard library is attractive.

Where Bun fits:

  1. CI speed (bun install, bun test).
  2. CLI tooling (bun build --compile).
  3. Edge cold start.
  4. SQLite-heavy small services.

Where Node 22+ still fits:

  1. Most enterprise production.
  2. Environments where APM/observability matter.
  3. Large monorepos with native addons.
  4. Company-standard runtime — easy to hire for.

The three runtimes are not zero-sum. Within one company, Node (main services), Bun (CLI/CI), and Deno (AI-agent sandbox, high-security pieces) often coexist. The "one wins everything" narrative no longer fits 2026.


Chapter 11 · The npm-Compat Broken / Over-Delivered Edges

Over-delivered

Broken / still weak


Chapter 12 · Who Is Running Deno 2 in Production

Case A — AI-agent sandbox

The clearest win zone. When permissions matter on model-generated code:

Case B — Full-stack on Deno Deploy

Small full-stack built with Fresh 2 plus Deno KV:

Case C — Backend tooling heavy on the standard library

CLI tools and dev tools that lean on @std/cli, @std/path, @std/fs, @std/encoding. Places where minimal external dependencies are valuable.

Case D — Slack/Discord bots and webhook handlers

Small webhook services with Deno.serve plus Deno.openKv(). Permissions keep the blast radius small; global deploy via Deno Deploy.

Case E — Almost none — large enterprise monoliths

Same reasons as Bun. APM, debugger, team familiarity, weight of an existing codebase.

Named cases (from public posts)


Chapter 13 · Migration — Steps for Moving a Node Project to Deno 2

Staged adoption

  1. Adopt deno fmt and deno lint only — no other tool changes. A low-friction first step.
  2. Run deno test in CI — in parallel with Vitest/Jest. Verify the speed and zero-config TS appeal.
  3. Move a single script to Deno — start with one-off scripts like scripts/migrate.ts.
  4. Write a new small service in Deno — new microservice or internal tool.
  5. Use deno install for dependency management — keep Node runtime, swap install only.
  6. Move production runtime to Deno — the biggest step, last.

Adoption checklist

Common anti-patterns


Epilogue — From Idealism to Pragmatism

Deno 1 was born as an explicit rebuttal to Node. Deno 2 withdrew half the rebuttal in favor of pragmatism. The result:

Deno gave up the "replace Node" narrative and adopted the "carve a meaningful space next to Node" narrative. That space is the AI-agent sandbox, Deno Deploy, and standard-library-centered tooling.

Things to remember:

Adoption checklist (before bringing Deno in)

Common anti-patterns (recap)

Next post previews


References

Deno official

Deno 2 launch materials

JSR

Fresh

Deno KV

Deno Deploy

Node.js (for comparison)

Bun (for comparison)

Security / permission model

Production adoption cases (from public posts)

Critical takes / retrospectives

Standards / WinterCG


Comments

No comments yet.

Sign in to leave a comment