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

Photo by Rahul Mishra on Unsplash
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.
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.
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.
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.
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.

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.
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.jsDeno 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.
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.
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 binarydeno 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.
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:
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.
| Concern | Deno 2 | Node.js |
|---|---|---|
| package.json and node_modules | Read natively, node_modules optional | Required and central to resolution |
| TypeScript | Runs .ts directly, no build step | Needs a loader, tsc, or bundler |
| Tooling | Formatter, linter, tests, compile built in | Assembled from separate npm packages |
| Package sources | npm, node, and jsr specifiers | npm registry, plus JSR via node_modules |
| Permissions | Secure by default, explicit flags | Full access by default |
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.