Deno 2.0 Complete Guide 2026: Setup, npm Support, Std Library & Deno Deploy
The complete hands-on guide to Deno 2.0 for 2026. Learn installation, npm package support, the standard library, TypeScript-first development, and deploying to Deno Deploy with real code examples.
Deno 2.0 shipped with one promise: make JavaScript runtime development feel like it was designed for 2026, not 2009. With native TypeScript support, a comprehensive standard library, npm compatibility, and a built-in deployment platform, Deno has transformed from an experimental project into a serious production runtime.
This guide walks you through everything you need to build and ship real Deno 2.0 applications β from first install to production deployment.
What Is Deno 2.0?
Deno is a JavaScript and TypeScript runtime built on V8 and Rust. Unlike Node.js, Deno was designed from scratch with modern developer experience as a first-class concern:
- No
node_modulesβ dependencies are fetched by URL or from the npm registry - TypeScript without config β runs
.tsfiles directly, notsconfig.jsonrequired - Security by default β file system, network, and environment access are denied unless explicitly granted
- Built-in tooling β formatter, linter, test runner, bundler all included
- npm compatibility β Deno 2.0 supports the full npm registry via
npm:specifiers
The 2.0 release was a major milestone: it introduced full Node.js/npm compatibility, a stable standard library, and a first-class package publishing workflow via JSR (JavaScript Registry).
Installation
macOS / Linux
curl -fsSL https://deno.land/install.sh | sh
Add to your shell profile:
export DENO_INSTALL="$HOME/.deno"
export PATH="$DENO_INSTALL/bin:$PATH"
Windows (PowerShell)
irm https://deno.land/install.ps1 | iex
Verify Installation
deno --version
# deno 2.x.x (release, aarch64-apple-darwin)
# v8 12.x.x
# typescript 5.x.x
Upgrading Deno
deno upgrade
# Upgrade to the latest stable version
deno upgrade --version 2.1.0
# Pin to a specific version
Your First Deno Program
Create hello.ts:
// hello.ts
const name = Deno.args[0] ?? "world";
console.log(`Hello, ${name}!`);
Run it:
deno run hello.ts
# Hello, world!
deno run hello.ts Deno
# Hello, Deno!
No build step. No configuration. TypeScript just works.
Deno 2.0 New Features
1. Full npm Compatibility
Deno 2.0 supports npm packages via the npm: prefix:
// Use any npm package directly
import express from "npm:express@4";
import chalk from "npm:chalk@5";
const app = express();
app.get("/", (_req, res) => {
res.send(chalk.green("Hello from Deno + Express!"));
});
app.listen(3000, () => {
console.log("Server running on http://localhost:3000");
});
Run with network permission:
deno run --allow-net server.ts
Deno downloads and caches npm packages automatically β no npm install needed.
2. Node.js Built-in Compatibility
Deno 2.0 supports node: specifiers for built-in Node.js modules:
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { createServer } from "node:http";
const content = readFileSync(join(Deno.cwd(), "README.md"), "utf-8");
console.log(content.slice(0, 100));
This means most Node.js code runs in Deno with minimal or no changes.
3. deno.json Configuration
Deno 2.0 introduced a proper configuration file:
{
"name": "my-app",
"version": "1.0.0",
"imports": {
"@std/path": "jsr:@std/path@1",
"@std/assert": "jsr:@std/assert@1",
"express": "npm:express@4"
},
"tasks": {
"dev": "deno run --watch --allow-all src/main.ts",
"test": "deno test --allow-all",
"fmt": "deno fmt",
"lint": "deno lint"
},
"fmt": {
"lineWidth": 100,
"singleQuote": true
},
"lint": {
"rules": {
"include": ["no-unused-vars", "no-explicit-any"]
}
}
}
4. JSR β JavaScript Registry
JSR is Denoβs package registry, built for the TypeScript-first world:
// Import from JSR β no npm, no CDN
import { join, dirname } from "jsr:@std/path@1";
import { assertEquals } from "jsr:@std/assert@1";
import { parse } from "jsr:@std/csv@1";
JSR packages:
- Are TypeScript-first (types included by default)
- Are cross-runtime (work in Deno, Node.js, and browsers)
- Have automated API documentation
The Deno Standard Library
Denoβs standard library (@std) is stable in 2.0 and covers everything you need without third-party dependencies.
File System
import { exists, ensureDir, copy } from "jsr:@std/fs@1";
// Check if file exists
if (await exists("./config.json")) {
console.log("Config found");
}
// Create directory recursively
await ensureDir("./output/reports");
// Copy a file
await copy("./template.html", "./output/index.html");
Path Utilities
import { join, extname, basename, dirname } from "jsr:@std/path@1";
const filePath = join(Deno.cwd(), "src", "main.ts");
console.log(extname(filePath)); // .ts
console.log(basename(filePath)); // main.ts
console.log(dirname(filePath)); // /path/to/src
HTTP Server
import { serve } from "jsr:@std/http@1";
const handler = async (req: Request): Promise<Response> => {
const url = new URL(req.url);
if (url.pathname === "/health") {
return new Response(JSON.stringify({ status: "ok" }), {
headers: { "Content-Type": "application/json" },
});
}
if (url.pathname === "/echo" && req.method === "POST") {
const body = await req.json();
return new Response(JSON.stringify({ echo: body }), {
headers: { "Content-Type": "application/json" },
});
}
return new Response("Not Found", { status: 404 });
};
console.log("Server running on http://localhost:8000");
await serve(handler, { port: 8000 });
CSV Parsing
import { parse, stringify } from "jsr:@std/csv@1";
const csvContent = `name,age,city
Alice,30,NYC
Bob,25,SF
Charlie,35,Chicago`;
const records = parse(csvContent, { skipFirstRow: true });
console.log(records);
// [{ name: "Alice", age: "30", city: "NYC" }, ...]
const output = stringify(records, { columns: ["name", "city"] });
console.log(output);
Date/Time
import { format, parse as parseDate } from "jsr:@std/datetime@1";
const now = new Date();
console.log(format(now, "yyyy-MM-dd HH:mm:ss"));
// "2026-03-28 14:30:00"
const date = parseDate("2026-03-28", "yyyy-MM-dd");
console.log(date.toISOString());
Security Model
Denoβs security model is explicit. By default, your program cannot access files, the network, environment variables, or run subprocesses.
Permission Flags
# Grant specific permissions
deno run --allow-net server.ts
deno run --allow-read=./data script.ts
deno run --allow-write=/tmp script.ts
deno run --allow-env=DATABASE_URL script.ts
deno run --allow-run=git script.ts
# Grant all permissions (use sparingly)
deno run --allow-all script.ts
# Prompt for permissions at runtime
deno run --prompt script.ts
Checking Permissions in Code
// Request permission programmatically
const readPerm = await Deno.permissions.request({ name: "read", path: "./data" });
if (readPerm.state === "granted") {
const content = await Deno.readTextFile("./data/config.json");
console.log(content);
} else {
console.error("Read permission denied");
}
// Check without prompting
const netPerm = await Deno.permissions.query({ name: "net", host: "api.example.com" });
console.log(netPerm.state); // "granted" | "denied" | "prompt"
Built-in Tooling
Formatter
deno fmt
deno fmt --check # Check without modifying
deno fmt src/ # Format specific directory
Linter
deno lint
deno lint src/
deno lint --rules-include=no-explicit-any
Test Runner
// math_test.ts
import { assertEquals, assertThrows } from "jsr:@std/assert@1";
function add(a: number, b: number): number {
return a + b;
}
function divide(a: number, b: number): number {
if (b === 0) throw new Error("Division by zero");
return a / b;
}
Deno.test("add: basic addition", () => {
assertEquals(add(2, 3), 5);
assertEquals(add(-1, 1), 0);
});
Deno.test("divide: throws on zero", () => {
assertThrows(() => divide(10, 0), Error, "Division by zero");
});
Deno.test({
name: "divide: async test example",
async fn() {
const result = await Promise.resolve(divide(10, 2));
assertEquals(result, 5);
},
});
deno test
deno test --coverage # Generate coverage report
deno test --watch # Watch mode
Documentation Generator
deno doc src/main.ts # Generate docs from JSDoc comments
deno doc --html src/ # Generate HTML docs
Building a REST API with Deno 2.0
Hereβs a complete REST API example using Denoβs built-in HTTP server:
// api/main.ts
interface User {
id: number;
name: string;
email: string;
}
const users: Map<number, User> = new Map([
[1, { id: 1, name: "Alice", email: "[email protected]" }],
[2, { id: 2, name: "Bob", email: "[email protected]" }],
]);
let nextId = 3;
function json(data: unknown, status = 200): Response {
return new Response(JSON.stringify(data), {
status,
headers: { "Content-Type": "application/json" },
});
}
async function handleRequest(req: Request): Promise<Response> {
const url = new URL(req.url);
const segments = url.pathname.split("/").filter(Boolean);
// GET /users
if (req.method === "GET" && segments[0] === "users" && !segments[1]) {
return json([...users.values()]);
}
// GET /users/:id
if (req.method === "GET" && segments[0] === "users" && segments[1]) {
const id = parseInt(segments[1]);
const user = users.get(id);
if (!user) return json({ error: "User not found" }, 404);
return json(user);
}
// POST /users
if (req.method === "POST" && segments[0] === "users") {
const body = await req.json() as Partial<User>;
if (!body.name || !body.email) {
return json({ error: "name and email required" }, 400);
}
const user: User = { id: nextId++, name: body.name, email: body.email };
users.set(user.id, user);
return json(user, 201);
}
// DELETE /users/:id
if (req.method === "DELETE" && segments[0] === "users" && segments[1]) {
const id = parseInt(segments[1]);
if (!users.has(id)) return json({ error: "User not found" }, 404);
users.delete(id);
return json({ deleted: id });
}
return json({ error: "Not Found" }, 404);
}
Deno.serve({ port: 8000 }, handleRequest);
console.log("API running at http://localhost:8000");
Run it:
deno run --allow-net api/main.ts
Environment Variables and Configuration
// config.ts
import { load } from "jsr:@std/dotenv@1";
// Load .env file
const env = await load();
export const config = {
port: parseInt(env.PORT ?? Deno.env.get("PORT") ?? "8000"),
databaseUrl: env.DATABASE_URL ?? Deno.env.get("DATABASE_URL") ?? "",
jwtSecret: env.JWT_SECRET ?? Deno.env.get("JWT_SECRET") ?? "dev-secret",
nodeEnv: env.NODE_ENV ?? Deno.env.get("NODE_ENV") ?? "development",
};
if (!config.databaseUrl) {
console.error("DATABASE_URL is required");
Deno.exit(1);
}
# Run with environment access
deno run --allow-env --allow-read=.env --allow-net app.ts
Deploying to Deno Deploy
Deno Deploy is Denoβs edge computing platform β globally distributed, serverless JavaScript execution with zero configuration.
Step 1: Create a Deno Deploy Account
Sign up at deno.com/deploy and link your GitHub account.
Step 2: Prepare Your Entry Point
// main.ts β Deno Deploy entry point
Deno.serve(async (req: Request) => {
const url = new URL(req.url);
if (url.pathname === "/") {
return new Response(
`<!DOCTYPE html>
<html>
<head><title>My Deno App</title></head>
<body>
<h1>Hello from Deno Deploy!</h1>
<p>Running on: ${Deno.version.deno}</p>
</body>
</html>`,
{ headers: { "Content-Type": "text/html" } }
);
}
if (url.pathname === "/api/time") {
return new Response(JSON.stringify({ time: new Date().toISOString() }), {
headers: { "Content-Type": "application/json" },
});
}
return new Response("Not Found", { status: 404 });
});
Step 3: Deploy via GitHub Integration
- Push your code to GitHub
- In Deno Deploy dashboard, click New Project
- Connect your repository
- Set entry point to
main.ts - Click Deploy
Your app is now live at https://your-project.deno.dev.
Step 4: Custom Domains
# Install deployctl CLI
deno install -A jsr:@deno/deployctl
# Deploy directly from CLI
deployctl deploy --project=my-app main.ts
# Deploy with environment variables
deployctl deploy --project=my-app \
--env DATABASE_URL=postgres://... \
main.ts
Step 5: Environment Variables in Deno Deploy
In the Deno Deploy dashboard:
- Go to Project Settings β Environment Variables
- Add your secrets (DATABASE_URL, API_KEY, etc.)
Access them in code:
const apiKey = Deno.env.get("API_KEY");
if (!apiKey) throw new Error("API_KEY not set");
KV Storage on Deno Deploy
Deno Deploy includes a globally distributed key-value store:
// Use Deno KV for persistent storage
const kv = await Deno.openKv();
interface Counter {
count: number;
updatedAt: string;
}
Deno.serve(async (req: Request) => {
const url = new URL(req.url);
if (url.pathname === "/counter" && req.method === "GET") {
const result = await kv.get<Counter>(["counter"]);
return new Response(JSON.stringify(result.value ?? { count: 0 }), {
headers: { "Content-Type": "application/json" },
});
}
if (url.pathname === "/counter/increment" && req.method === "POST") {
const current = await kv.get<Counter>(["counter"]);
const newCount = (current.value?.count ?? 0) + 1;
await kv.set(["counter"], {
count: newCount,
updatedAt: new Date().toISOString(),
});
return new Response(JSON.stringify({ count: newCount }), {
headers: { "Content-Type": "application/json" },
});
}
return new Response("Not Found", { status: 404 });
});
Migrating from Node.js
Most Node.js code migrates to Deno 2.0 with minimal changes:
Package Imports
// Node.js
import express from "express";
import { readFileSync } from "fs";
import path from "path";
// Deno 2.0 (option 1 β use node: prefix)
import express from "npm:express";
import { readFileSync } from "node:fs";
import path from "node:path";
// Deno 2.0 (option 2 β use Deno APIs)
import express from "npm:express";
const content = await Deno.readTextFile("./file.txt");
import { join } from "jsr:@std/path@1";
package.json β deno.json
// package.json scripts
{
"scripts": {
"start": "node dist/index.js",
"dev": "ts-node src/index.ts",
"test": "jest"
}
}
// deno.json tasks
{
"tasks": {
"start": "deno run --allow-all src/main.ts",
"dev": "deno run --watch --allow-all src/main.ts",
"test": "deno test --allow-all"
}
}
Common Patterns
// Reading JSON (Node.js)
const config = JSON.parse(readFileSync("./config.json", "utf-8"));
// Reading JSON (Deno)
const config = JSON.parse(await Deno.readTextFile("./config.json"));
// Or with permission check
import { exists } from "jsr:@std/fs@1";
if (await exists("./config.json")) {
const config = JSON.parse(await Deno.readTextFile("./config.json"));
}
Performance Tips
1. Use --cached-only in CI
# Pre-cache dependencies
deno cache src/main.ts
# Run without network access in CI
deno run --allow-all --cached-only src/main.ts
2. Compile to a Single Binary
# Compile to standalone executable
deno compile --allow-all --output my-app src/main.ts
# Cross-compile for different targets
deno compile --target x86_64-pc-windows-msvc --allow-all --output my-app.exe src/main.ts
deno compile --target x86_64-unknown-linux-gnu --allow-all --output my-app-linux src/main.ts
3. Use Workers for CPU-Intensive Tasks
// worker.ts
self.onmessage = (event) => {
const { data } = event;
const result = expensiveComputation(data);
self.postMessage(result);
};
function expensiveComputation(n: number): number {
// Simulate CPU work
return Array.from({ length: n }, (_, i) => i).reduce((a, b) => a + b, 0);
}
// main.ts
const worker = new Worker(new URL("./worker.ts", import.meta.url), {
type: "module",
});
worker.postMessage(1_000_000);
worker.onmessage = (event) => {
console.log("Result:", event.data);
worker.terminate();
};
Summary
Deno 2.0 delivers on its promise of a modern JavaScript runtime:
| Feature | Details |
|---|---|
| TypeScript support | Native, zero config |
| npm compatibility | Full via npm: prefix |
| Node.js built-ins | Supported via node: prefix |
| Standard library | Stable, JSR-hosted |
| Security model | Explicit permissions |
| Built-in tooling | fmt, lint, test, doc, compile |
| Deployment | Deno Deploy (edge, global) |
Whether youβre starting a new project or migrating from Node.js, Deno 2.0 provides a clean, secure, and modern development experience with first-class TypeScript support and a batteries-included standard library.
Target keywords: deno 2.0 guide, deno vs node 2026, deno 2 npm support, deno deploy tutorial, deno typescript guide
50 CLI Commands That Save Hours Every Week
The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.
- Git mastery (10 cmds)
- File & search wizardry
- Docker & DevOps tricks
- Process & network tools
- Productivity boosters
- macOS & Linux install guide
No spam. 1 email/week max. Unsubscribe anytime.
Found this guide useful? Check out our free developer tools.
Affiliate disclosure: Some links below are affiliate links β we may earn a small commission at no extra cost to you. Learn more.
Recommended Tools & Resources
DigitalOcean
$200 credit for new users. Simple, affordable cloud hosting for developers.
GitHub Student Pack
Free access to 100+ developer tools. Perfect for students and new devs.
Vercel
Deploy frontend apps instantly. Free tier is generous for side projects.
DevPlaybook Products
Boilerplates, scripts & AI toolkits to 10x your dev workflow.