Deno 2 is a different runtime from what Deno was in 2020. The original version launched with a hard stance against node_modules and npm. The vision was clean but impractical — most real-world JavaScript code assumes npm packages exist.
Deno 2 walked that back. You can now run npm packages, import from npm directly, use node_modules, and run most Node.js code without modification.
What Changed in Deno 2
-
npm packages work —
import { express } from 'npm:express'or install intonode_modules -
Node.js APIs implemented —
fs,path,http,crypto,stream, and more -
node_modules support —
deno installcreates a propernode_modulesdirectory -
Workspaces — monorepo support with multiple
deno.jsonconfigs -
JSR — the new TypeScript-first registry, replacing
deno.land/x - LTS releases — stable tracks for production
Installation
# macOS / Linux
curl -fsSL https://deno.land/install.sh | sh
# Windows
irm https://deno.land/install.ps1 | iex
deno --version
Package Management
npm Packages
// No install needed — resolved on first run, cached globally
import express from 'npm:express@4'
import { z } from 'npm:zod'
// Or install explicitly for editor tooling
deno install npm:express npm:zod npm:hono
JSR — TypeScript-First Registry
deno add jsr:@std/path
deno add jsr:@hono/hono
import { join } from 'jsr:@std/path'
deno.json
{
"imports": {
"@/": "./src/",
"hono": "jsr:@hono/hono@^4",
"zod": "npm:zod@^3"
},
"tasks": {
"dev": "deno run --watch --allow-net src/main.ts",
"build": "deno compile --allow-net src/main.ts"
}
}
HTTP Server
Deno.serve({ port: 3000 }, async (req: Request) => {
const url = new URL(req.url)
if (url.pathname === '/api/health') {
return Response.json({ status: 'ok' })
}
return new Response('Not Found', { status: 404 })
})
With Hono
import { Hono } from 'jsr:@hono/hono'
import { zValidator } from 'npm:@hono/zod-validator'
import { z } from 'npm:zod'
const app = new Hono()
app.get('/api/posts', async (c) => {
const posts = await db.query('SELECT * FROM posts WHERE published = 1')
return c.json(posts)
})
app.post('/api/posts',
zValidator('json', z.object({ title: z.string(), content: z.string() })),
async (c) => {
const data = c.req.valid('json')
const post = await db.insert(data)
return c.json(post, 201)
}
)
Deno.serve({ port: 3000 }, app.fetch)
Permissions Model
Deno's security model is explicit — code can't access the network, file system, or environment without you granting permission:
deno run --allow-net main.ts
deno run --allow-read=./src,./config main.ts
deno run --allow-write=/tmp main.ts
deno run --allow-env=DATABASE_URL,PORT main.ts
# Allow everything (dev only)
deno run -A main.ts
Practical tip: Start with -A during development, then lock down permissions before shipping.
File APIs
const text = await Deno.readTextFile('./config.json')
const data = JSON.parse(text)
await Deno.writeTextFile('./output.txt', 'Hello, Deno!')
// Append
await Deno.writeTextFile('./log.txt', 'entry\n', { append: true })
// Directory
for await (const entry of Deno.readDir('./src')) {
console.log(entry.name, entry.isFile)
}
Built-in Toolchain
deno fmt # format (like Prettier)
deno fmt --check # CI check
deno lint # lint
deno check main.ts # type-check
deno test # run tests
deno doc src/utils.ts # generate docs
Compile to Binary
# Standalone binary — no Deno runtime needed on target machine
deno compile --allow-net --output my-app src/main.ts
# Cross-compile
deno compile --target x86_64-unknown-linux-gnu --output app-linux src/main.ts
deno compile --target x86_64-pc-windows-msvc --output app.exe src/main.ts
Deno KV
Built-in key-value store, available locally and on Deno Deploy:
const kv = await Deno.openKv()
await kv.set(['users', userId], { name: 'Alice', email: 'alice@example.com' })
const result = await kv.get(['users', userId])
console.log(result.value?.name)
const entries = kv.list({ prefix: ['users'] })
for await (const entry of entries) {
console.log(entry.key, entry.value)
}
Deno vs Node.js vs Bun
| Deno 2 | Node.js | Bun | |
|---|---|---|---|
| TypeScript | Native | Requires tsx | Native |
| Formatting | Built-in | Prettier | None |
| Linting | Built-in | ESLint | None |
| Test runner | Built-in | Jest/Vitest | Built-in |
| SQLite | jsr:@db/sqlite | better-sqlite3 | Built-in |
| Permissions | Explicit (sandboxed) | None | None |
| Binary compile | Yes (deno compile) | pkg | Yes |
| Edge deploy | Deno Deploy | Vercel/Railway | Cloudflare Workers |
| npm compat | High | Native | High |
| Maturity | Growing | Mature | Growing |
Choose Deno 2 if you want a secure-by-default runtime with built-in tooling. The permissions model and built-in formatter/linter/tester are genuinely useful for teams.
Full article at stacknotice.com/blog/deno-2-complete-guide-2026
Top comments (0)