Skip to main content
BlogSoftware Development

Deno 2: Node.js and npm Compatibility for Existing Projects

Deno 2 reads package.json and node_modules, imports npm and JSR packages directly, and ships one CLI for fmt, lint, test, and compile. Here is why it is now a real drop-in for Node projects.

Matthews Wong

Software Engineer

July 20269 min read
Deno 2: Node.js and npm Compatibility for Existing Projects

Photo by Rahul Mishra on Unsplash

Key Takeaway

Deno 2, released October 2024, adds full backwards compatibility with Node.js and npm. It reads package.json and node_modules, imports packages via npm and jsr specifiers, and bundles formatter, linter, test runner, and compiler into one CLI, making it a viable drop-in for existing Node projects.

For years the honest answer to whether you could run a real Node codebase on Deno was no. The runtime was elegant, secure by default, and TypeScript-native, but it ignored package.json, refused a node_modules folder, and expected you to rewrite imports around URLs. That was a wall most teams would not climb for an app already shipping in production.

Deno 2, released in October 2024, tears that wall down. The headline of the release is backwards compatibility with Node.js and npm: you can point Deno at an existing project and it just runs. This post walks through what actually changed and why I now treat Deno as a serious option for code I did not write from scratch.

package.json and node_modules are first-class now

The single biggest change is that Deno 2 understands the files a Node project already has. It reads your package.json, respects its dependencies and scripts, and will create and use a node_modules directory when your tooling expects one. There is no conversion step and no separate manifest to maintain.

In practice you drop into a Node repository and the familiar commands have direct equivalents. deno install reads package.json and populates node_modules, and deno task runs the scripts you already defined under the scripts field, the same way npm run does.

# An existing Node project — nothing to rewrite
deno install                 # reads package.json, populates node_modules
deno task dev                # runs the "dev" script from package.json
deno run --allow-net server.js

Deno also honors package.json fields like the type field for module resolution and engines for version constraints, and it supports private registries through your existing .npmrc file. The result is that an ESM Node app frequently runs under Deno with zero code changes.

npm, node, and jsr specifiers side by side

Deno 2 resolves three kinds of imports without any build tooling. The npm prefix pulls any of the two million-plus packages on the npm registry straight from your source, downloading on first run into a global cache. The node prefix reaches Node built-ins like fs and process, and the jsr prefix pulls from JSR, the TypeScript-native registry.

// npm packages — no install step required
import express from "npm:express@4";
import chalk from "npm:chalk@5";

// Node built-ins via the node: prefix
import { readFile } from "node:fs/promises";
import process from "node:process";

// JSR — the TypeScript-native registry
import { serveDir } from "jsr:@std/http/file-server";

You do not always need npm specifiers. Once a package.json lists a dependency, Deno lets you import it with a plain bare specifier just like Node does, so most existing import lines keep working untouched.

One CLI replaces the whole toolchain

The part I appreciate most as a maintainer is that Deno ships a complete toolchain in a single binary. No separate Prettier, ESLint, Jest, and bundler to wire together and keep in sync. The deno command formats, lints, tests, adds dependencies, and compiles.

deno add npm:zod        # add an npm dependency
deno add jsr:@std/assert  # add a JSR dependency
deno fmt                # format JS, TS, JSON, Markdown, HTML, CSS, YAML
deno lint               # lint with Node-specific rules
deno test               # runs Deno tests AND node:test suites
deno compile --output app main.ts   # single self-contained binary

deno fmt now formats JavaScript, TypeScript, JSON, Markdown, HTML, CSS, and YAML. deno lint added Node-specific rules. deno test runs both Deno tests and node:test suites. deno compile produces a single self-contained executable, and deno task runs your package.json scripts. It is one dependency to install and upgrade instead of a dozen.

Workspaces and hybrid monorepos

Deno 2 supports monorepos through a workspace field in a root deno.json. A workspace is a collection of member folders, and crucially each member can carry either a deno.json or a package.json, so you can mix Deno-native and Node-native packages in the same repository.

// deno.json at the repo root
{
  "workspace": ["./api", "./web", "./packages/shared"]
}

That hybrid model is what makes gradual adoption realistic. You can migrate one package at a time rather than converting an entire monorepo in a single risky change. Across a workspace Deno can:

  • Resolve dependencies between member packages by their scoped names.
  • Run tests, formatting, and linting across every member with one command.
  • Publish individual members to JSR while others stay on npm.

Long-term stability is now an explicit promise

The other thing enterprises ask for is a stability guarantee, and Deno 2 answers it. Starting with Deno 2.1 the project ships long-term support releases: critical bug fixes are back-ported for six months, and a new LTS branch is cut every six months. Paired with a heavily audited standard library published on JSR, that gives teams a predictable upgrade cadence.

Compatibility is high but not total. Some packages lean on deep Node internals or native addons that still have rough edges, so pilot a non-critical service first and run your full test suite under Deno before you commit a production workload.

Deno 2 versus Node.js at a glance

ConcernDeno 2Node.js
package.json and node_modulesRead natively, node_modules optionalRequired and central to resolution
TypeScriptRuns .ts directly, no build stepNeeds a loader, tsc, or bundler
ToolingFormatter, linter, tests, compile built inAssembled from separate npm packages
Package sourcesnpm, node, and jsr specifiersnpm registry, plus JSR via node_modules
PermissionsSecure by default, explicit flagsFull access by default

So should you switch?

Deno 2 reframes the question. It is no longer switch or stay, but how much of your existing Node stack you want to keep. Because it reads package.json, uses node_modules, and imports from npm, you can adopt it incrementally and reverse course cheaply if it does not fit.

My recommendation is pragmatic: try Deno on a small internal service or a fresh package in your monorepo. If deno fmt, deno lint, deno test, and deno compile let you delete a pile of config and dev dependencies, that simplification alone is worth the experiment, and you keep every escape hatch back to Node.

FAQ

Frequently Asked Questions

Short answers to what readers ask most about this topic.

Can I run an existing Node.js project on Deno 2 without changes?

Often yes. Deno 2 reads package.json, respects its dependencies and scripts, and creates a node_modules directory when needed. Most ESM-based Node apps run under Deno with zero code changes, though packages relying on deep Node internals or native addons should be tested first.

What is the difference between npm:, node:, and jsr: specifiers in Deno 2?

The npm: prefix imports any package from the npm registry directly from your source. The node: prefix imports Node built-in modules like fs and process. The jsr: prefix imports from JSR, the TypeScript-native registry. Deno 2 resolves all three without a build step.

Does Deno 2 support node_modules and package.json?

Yes. Deno 2 added first-class support for both. It reads package.json fields such as dependencies, scripts, type, and engines, and will populate a node_modules folder when your tooling expects one. This is the core of its Node.js and npm backwards compatibility.

What tools are built into the Deno 2 CLI?

A single deno binary includes a formatter (deno fmt), linter (deno lint), test runner (deno test, which also runs node:test suites), dependency management (deno add, deno install), a task runner (deno task), and a compiler (deno compile) that produces a self-contained executable.

Does Deno 2 offer long-term support for stability?

Yes. Starting with Deno 2.1, the project ships long-term support releases. Critical bug fixes are back-ported for six months, and a new LTS branch is created every six months, giving teams a predictable upgrade cadence alongside an audited standard library on JSR.