Deno is a modern JavaScript, TypeScript, and WebAssembly runtime built on V8, Rust, and Tokio. It runs scripts in a secure sandbox by default, ships native TypeScript support without a separate compiler, and includes a complete toolchain — formatter, linter, test runner, benchmarker, bundler, documentation generator, package manager, and compiler — in a single executable. Programs that ran in Node.js can usually run in Deno with minimal changes via the built-in node: and npm: import schemes.
This guide walks you from installation through everyday use to advanced workflows. Written from the Deno 2.7.12 source at commit 7529c84 (2026-04-17).
1. Quick Start
The fastest path from zero to a running Deno program.
1.1 Install Deno (60 Seconds)
macOS / Linux
curl -fsSL https://deno.land/install.sh | sh
Windows (PowerShell)
irm https://deno.land/install.ps1 | iex
Homebrew
brew install deno
Cargo (from source)
cargo install deno --locked
Verify the installation:
deno --version
Expected output:
deno 2.7.12 (stable, release, x86_64-unknown-linux-gnu)
v8 13.7.152.6
typescript 5.7.3
Tip
Deno installs as a single binary with zero external dependencies. If you need to install in a CI pipeline, the one-liner shell scripts above are all you need — no package manager lock files, no
node_modules, no post-install hooks.
1.2 Hello World
Create a file called hello.ts:
console.log("Hello from Deno!");
Run it:
deno run hello.ts
Hello from Deno!
TypeScript works out of the box with no tsconfig.json, no tsc, and no build step.
1.3 Fetch Data from the Network
// fetch_example.ts
const response = await fetch("https://api.github.com/zen");
const text = await response.text();
console.log(`GitHub says: ${text}`);
deno run --allow-net=api.github.com fetch_example.ts
GitHub says: Speak like a human.
Notice the --allow-net flag — Deno blocks network access by default. You must explicitly grant permissions. This is the cornerstone of Deno's security model.
1.4 Run a Remote Script
Deno can run scripts directly from URLs:
deno run https://examples.deno.land/hello-world.ts
Hello, World!
The module is downloaded, cached locally, and executed. Subsequent runs use the cached version unless you pass --reload.
1.5 Initialize a New Project
deno init my_project
✅ Project initialized
Run these commands to get started
cd my_project
deno run main.ts
deno task dev
This creates a deno.json configuration file, a main.ts entry point, and a main_test.ts test file.
2. Installation
2.1 System Requirements
| Platform | Architectures | Minimum |
|---|---|---|
| macOS | x86_64, aarch64 (Apple Silicon) | macOS 10.15+ |
| Linux | x86_64, aarch64 | glibc 2.31+ or musl |
| Windows | x86_64 | Windows 10+ |
Deno is a single static binary. It requires no system libraries beyond what ships with the OS.
2.2 Installation Methods
Shell Installer (Recommended)
macOS / Linux:
curl -fsSL https://deno.land/install.sh | sh
Windows (PowerShell):
irm https://deno.land/install.ps1 | iex
To install a specific version:
curl -fsSL https://deno.land/install.sh | sh -s v2.7.12
Important
After installation, add Deno to your PATH. The installer prints the exact commands for your shell. For bash/zsh, add
export DENO_INSTALL="$HOME/.deno"andexport PATH="$DENO_INSTALL/bin:$PATH"to your shell profile (~/.bashrc,~/.zshrc, or~/.bash_profile).
Package Managers
# Homebrew (macOS/Linux)
brew install deno
# Chocolatey (Windows)
choco install deno
# Scoop (Windows)
scoop install deno
# WinGet (Windows)
winget install DenoLand.Deno
# Nix
nix-shell -p deno
# asdf
asdf plugin add deno
asdf install deno 2.7.12
asdf global deno 2.7.12
Docker
docker run -it denoland/deno:2.7.12 deno run https://examples.deno.land/hello-world.ts
For a project:
FROM denoland/deno:2.7.12
WORKDIR /app
COPY . .
RUN deno cache main.ts
CMD ["deno", "run", "--allow-net", "--allow-read", "main.ts"]
Build from Source
git clone https://github.com/denoland/deno.git
cd deno
cargo build --release
./target/release/deno --version
Requires Rust 1.77+ and the protobuf-compiler system package.
2.3 Upgrading
deno upgrade
Looking up latest version
Found latest version 2.7.12
Deno is already up to date
Upgrade to a specific version:
deno upgrade --version 2.7.12
Upgrade to the canary (bleeding-edge) channel:
deno upgrade --canary
Warning
Canary builds are compiled from the latest commit on
mainand may contain breaking changes or unfinished features. Do not use canary in production. SetDENO_CANARY=1in your environment to makedeno upgradedefault to the canary channel.
2.4 Shell Completions
Generate completions for your shell and source them:
# Bash
deno completions bash > /usr/local/etc/bash_completion.d/deno
# Zsh
deno completions zsh > ~/.zsh/completions/_deno
# Fish
deno completions fish > ~/.config/fish/completions/deno.fish
# PowerShell
deno completions powershell >> $PROFILE
2.5 Uninstalling
Remove the binary and cache:
rm -rf ~/.deno
# or remove the binary directly
rm $(which deno)
On Windows, delete %USERPROFILE%\.deno and remove the PATH entry.
3. Configuration
Deno uses a deno.json (or deno.jsonc) configuration file at the project root. This file is optional — every setting has sensible defaults — but it centralizes project configuration in one place.
3.1 Creating a Configuration File
deno init
This creates a minimal deno.json:
{
"tasks": {
"dev": "deno run --watch main.ts"
}
}
3.2 Configuration File Reference
A comprehensive deno.json with all major sections:
{
// Import map — resolve bare specifiers
"imports": {
"@std/": "jsr:@std/",
"oak": "jsr:@oak/oak@^17",
"lodash": "npm:lodash@^4.17"
},
// Tasks (like npm scripts)
"tasks": {
"dev": "deno run --watch --allow-net main.ts",
"start": "deno run --allow-net --allow-read main.ts",
"test": "deno test --allow-read",
"lint": "deno lint",
"fmt": "deno fmt",
"build": "deno compile --output=dist/app main.ts"
},
// TypeScript compiler options
"compilerOptions": {
"strict": true,
"lib": ["deno.window", "deno.unstable"],
"jsx": "react-jsx",
"jsxImportSource": "react",
"experimentalDecorators": true
},
// Formatter settings
"fmt": {
"useTabs": false,
"lineWidth": 80,
"indentWidth": 2,
"singleQuote": false,
"proseWrap": "always",
"include": ["src/"],
"exclude": ["vendor/"]
},
// Linter settings
"lint": {
"include": ["src/"],
"exclude": ["vendor/"],
"rules": {
"tags": ["recommended"],
"include": ["ban-untagged-todo", "no-unused-vars"],
"exclude": ["no-explicit-any"]
}
},
// Test runner settings
"test": {
"include": ["tests/"],
"exclude": ["tests/fixtures/"]
},
// Benchmark settings
"bench": {
"include": ["benches/"],
"exclude": []
},
// Lock file (true by default)
"lock": true,
// Node modules mode
"nodeModulesDir": "auto",
// Workspace (monorepo)
"workspace": ["./packages/core", "./packages/cli"],
// Unstable features
"unstable": ["kv", "cron", "temporal"]
}
Note
For the full configuration schema with every supported key and its valid values, see the Admin Guide section on Configuration Mastery. This User Guide covers the options most people need day-to-day.
3.3 Import Maps
Import maps let you use bare specifiers (like "lodash") instead of full URLs. They live in the "imports" field of deno.json:
{
"imports": {
"@std/path": "jsr:@std/path@^1",
"@std/assert": "jsr:@std/assert@^1",
"express": "npm:express@^4.18",
"~/": "./src/"
}
}
Then in your code:
import { join } from "@std/path";
import { assertEquals } from "@std/assert";
import express from "express";
import { helper } from "~/utils/helper.ts";
Scoped imports let you remap dependencies of dependencies:
{
"imports": {
"lodash": "npm:lodash@4.17.21"
},
"scopes": {
"npm:some-package@1/": {
"lodash": "npm:lodash@3.10.0"
}
}
}
3.4 Tasks
Tasks are the Deno equivalent of npm scripts. Define them in deno.json and run with deno task:
{
"tasks": {
"dev": "deno run --watch --allow-net main.ts",
"start": "deno run --allow-net --allow-read main.ts",
"test": "deno test --allow-read --parallel",
"test:coverage": "deno test --allow-read --coverage=cov && deno coverage cov",
"lint": "deno lint",
"fmt": "deno fmt --check",
"check": "deno check main.ts",
"build": "deno compile --output dist/app main.ts",
"clean": "rm -rf dist/ cov/"
}
}
deno task dev
Task dev deno run --watch --allow-net main.ts
Watcher Process started.
Listening on http://localhost:8000
Tasks support shell syntax including pipes, &&, ||, and environment variables:
{
"tasks": {
"ci": "deno lint && deno fmt --check && deno test --parallel",
"greet": "echo Hello $USER"
}
}
List all available tasks:
deno task
Available tasks:
- dev
deno run --watch --allow-net main.ts
- start
deno run --allow-net --allow-read main.ts
- test
deno test --allow-read --parallel
3.5 Lock Files
Deno generates a deno.lock (v2 format) file automatically to ensure reproducible builds:
# Generate or update the lock file
deno cache --lock=deno.lock main.ts
# Verify integrity against the lock file
deno cache --lock=deno.lock --frozen main.ts
The lock file records the hash of every remote dependency. If a dependency changes upstream (supply-chain attack, accidental update), Deno refuses to run:
error: The source code is invalid, as it does not match the expected hash in the lock file.
Specifier: https://deno.land/std@0.224.0/path/mod.ts
To update:
deno cache --lock=deno.lock --lock-write main.ts
Disable lock files entirely in deno.json:
{
"lock": false
}
3.6 Workspaces (Monorepos)
Deno supports monorepos through workspace configuration:
// deno.json (root)
{
"workspace": ["./packages/core", "./packages/cli", "./packages/web"]
}
// packages/core/deno.json
{
"name": "@myorg/core",
"version": "1.0.0",
"exports": "./mod.ts"
}
// packages/cli/deno.json
{
"name": "@myorg/cli",
"version": "1.0.0",
"exports": "./mod.ts",
"imports": {
"@myorg/core": "jsr:@myorg/core@^1"
}
}
Each workspace member has its own deno.json but shares the root lock file and can reference sibling packages.
3.7 TypeScript Configuration
Deno ships with built-in TypeScript support. The compilerOptions field in deno.json accepts a subset of tsconfig.json options:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"lib": ["deno.window"],
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}
Supported lib values include deno.window, deno.worker, deno.unstable, deno.ns, and esnext. These control which type declarations are available.
Tip
You do not need a
tsconfig.jsonfile. Deno's defaults (strict: true, targetingesnext) are intentionally modern. Only addcompilerOptionswhen you need JSX, decorators, or specific strictness tweaks.
3.8 Node Modules Directory
By default, Deno caches npm packages globally in DENO_DIR. If you prefer a local node_modules directory (for compatibility with tools that expect it):
{
"nodeModulesDir": "auto"
}
| Value | Behavior |
|---|---|
"none" |
Global cache only (default) |
"auto" |
Create node_modules automatically when npm packages are used |
"manual" |
Create node_modules only when you run deno install |
4. Core Usage
4.1 Running Scripts
Basic Execution
deno run main.ts
Deno runs TypeScript, JavaScript, JSX, and TSX files natively. File extensions determine the parser:
| Extension | Language |
|---|---|
.ts |
TypeScript |
.tsx |
TypeScript + JSX |
.js |
JavaScript |
.jsx |
JavaScript + JSX |
.mjs |
JavaScript (ES module) |
.mts |
TypeScript (ES module) |
.wasm |
WebAssembly |
Watch Mode
Automatically restart when files change:
deno run --watch main.ts
Watcher Process started.
Listening on http://localhost:8000
Watcher File change detected! Restarting!
Listening on http://localhost:8000
Watch specific files or directories:
deno run --watch=src/,config/ main.ts
Running Remote Modules
# From URL
deno run https://deno.land/std/examples/welcome.ts
# From JSR
deno run jsr:@std/http/file-server
# Force re-download
deno run --reload https://deno.land/std/examples/welcome.ts
Evaluating Code Inline
deno eval "console.log(1 + 2)"
3
deno eval --ext=ts "const x: number = 42; console.log(x)"
42
4.2 The Permission System
Deno runs in a secure sandbox by default. All access to the outside world requires explicit permission grants.
Permission Flags
| Flag | Scope | Example |
|---|---|---|
--allow-read |
File system reads | --allow-read=/etc,/tmp |
--allow-write |
File system writes | --allow-write=./output |
--allow-net |
Network access | --allow-net=api.github.com:443 |
--allow-env |
Environment variables | --allow-env=HOME,PATH |
--allow-run |
Subprocess execution | --allow-run=git,npm |
--allow-sys |
System info (hostname, OS, etc.) | --allow-sys=hostname,osRelease |
--allow-ffi |
Foreign function interface | --allow-ffi=./libfoo.so |
--allow-import |
Remote module imports | --allow-import=deno.land,jsr.io |
-A / --allow-all |
Everything | (use sparingly) |
Deny Flags
Deny flags take precedence over allow flags, enabling "allow everything except":
| Flag | Purpose |
|---|---|
--deny-read |
Block specific paths |
--deny-write |
Block specific paths |
--deny-net |
Block specific hosts |
--deny-env |
Block specific variables |
--deny-run |
Block specific executables |
--deny-sys |
Block specific system info |
--deny-ffi |
Block FFI for specific libraries |
Warning
-A/--allow-alldisables the entire permission sandbox. Only use this for trusted code during development. In production, always specify the minimum permissions needed. The deny flags cannot override--allow-all.
Granular Permission Scoping
Permissions accept comma-separated allow-lists:
# Allow reading only the data directory and /etc/hosts
deno run --allow-read=./data,/etc/hosts main.ts
# Allow network to specific hosts and ports
deno run --allow-net=api.github.com:443,localhost:8080 main.ts
# Allow running only git and curl
deno run --allow-run=git,curl main.ts
# Allow reading specific env vars
deno run --allow-env=DATABASE_URL,API_KEY main.ts
Combining Allow and Deny
# Allow all file reads EXCEPT /etc/shadow and /etc/passwd
deno run --allow-read --deny-read=/etc/shadow,/etc/passwd main.ts
# Allow all network EXCEPT internal services
deno run --allow-net --deny-net=192.168.0.0/16,10.0.0.0/8 main.ts
Note
Deny flags always win. If both
--allow-read=/etcand--deny-read=/etc/shadoware set, Deno allows reading everything under/etcexcept/etc/shadow.
Interactive Permission Prompts
When you run a script without permission flags and it requests access, Deno prompts:
┌ ⚠️ Deno requests net access to "api.github.com:443".
├ Requested by `fetch()` API.
├ Run again with --allow-net to bypass this prompt.
└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net)
This lets you run untrusted scripts and approve permissions one at a time. In CI, the prompt is skipped and access is denied (non-interactive mode).
4.3 Module System
ES Modules Everywhere
Deno uses standard ES modules. Every file is a module. No CommonJS (require), no global namespace pollution.
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
// main.ts
import { add, multiply } from "./math.ts";
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
Important
Deno requires file extensions in local imports. Write
import { add } from "./math.ts", notimport { add } from "./math". This matches the web platform and avoids ambiguity.
Import Sources
Deno supports three import sources:
// 1. Local files (relative or absolute path)
import { helper } from "./utils/helper.ts";
// 2. JSR (JavaScript Registry) — preferred for Deno-native packages
import { serve } from "jsr:@std/http@^1/serve";
import { parse } from "jsr:@std/flags@^1";
// 3. npm packages — full Node.js ecosystem access
import express from "npm:express@^4.18";
import chalk from "npm:chalk@^5";
// 4. HTTPS URLs (pin versions via lock file)
import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";
Re-exporting
// mod.ts — barrel file
export { serve } from "./server.ts";
export { Router } from "./router.ts";
export type { Route, Handler } from "./types.ts";
Dynamic Imports
const module = await import("./plugins/analytics.ts");
module.track("pageview");
4.4 Standard Library
The Deno Standard Library (@std) provides reviewed, stable modules. Import from JSR:
import { join, resolve, basename } from "@std/path";
import { assertEquals } from "@std/assert";
import { parse as parseFlags } from "@std/flags";
import { serveDir } from "@std/http/file-server";
import { copy } from "@std/fs/copy";
import { readLines } from "@std/io/read-lines";
import { delay } from "@std/async/delay";
import { format } from "@std/datetime/format";
import { encodeBase64 } from "@std/encoding/base64";
import { crypto } from "@std/crypto";
Add standard library packages to your project:
deno add jsr:@std/path jsr:@std/assert jsr:@std/http
This updates the imports map in deno.json:
{
"imports": {
"@std/assert": "jsr:@std/assert@^1.0.12",
"@std/http": "jsr:@std/http@^1.0.16",
"@std/path": "jsr:@std/path@^1.0.10"
}
}
4.5 npm Compatibility
Deno can use the entire npm ecosystem.
Importing npm Packages
// Using npm: specifier directly
import express from "npm:express@^4.18";
import chalk from "npm:chalk@^5";
import _ from "npm:lodash@^4.17";
Or add them to your import map:
deno add npm:express npm:chalk
{
"imports": {
"chalk": "npm:chalk@^5.4.1",
"express": "npm:express@^4.21.2"
}
}
Then import cleanly:
import express from "express";
import chalk from "chalk";
Full Express.js Example
// server.ts
import express from "npm:express@^4.18";
const app = express();
app.get("/", (_req, res) => {
res.json({ message: "Hello from Deno + Express!" });
});
app.listen(3000, () => {
console.log("Server running on http://localhost:3000");
});
deno run --allow-net --allow-read --allow-env server.ts
Server running on http://localhost:3000
4.6 Node.js Compatibility
Deno polyfills 48 Node.js built-in modules. Import them with the node: prefix:
import fs from "node:fs";
import path from "node:path";
import { createServer } from "node:http";
import { EventEmitter } from "node:events";
import { Buffer } from "node:buffer";
import crypto from "node:crypto";
import { Readable, Writable } from "node:stream";
import { Worker } from "node:worker_threads";
import os from "node:os";
import url from "node:url";
import util from "node:util";
import assert from "node:assert";
The full list of polyfilled modules:
| Module | Module | Module | Module |
|---|---|---|---|
node:assert |
node:async_hooks |
node:buffer |
node:child_process |
node:cluster |
node:console |
node:constants |
node:crypto |
node:dgram |
node:diagnostics_channel |
node:dns |
node:domain |
node:events |
node:fs |
node:http |
node:http2 |
node:https |
node:inspector |
node:module |
node:net |
node:os |
node:path |
node:perf_hooks |
node:process |
node:punycode |
node:querystring |
node:readline |
node:repl |
node:stream |
node:string_decoder |
node:sys |
node:test |
node:timers |
node:tls |
node:trace_events |
node:tty |
node:url |
node:util |
node:v8 |
node:vm |
node:wasi |
node:worker_threads |
node:zlib |
Using CommonJS Modules
For npm packages that use require():
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const legacyLib = require("some-cjs-package");
4.7 Web APIs
Deno implements standard Web APIs so code is portable between Deno, browsers, and other runtimes:
// fetch (WHATWG Fetch API)
const res = await fetch("https://api.github.com/zen");
// Web Crypto
const key = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"],
);
// Streams
const stream = new ReadableStream({
start(controller) {
controller.enqueue("Hello");
controller.enqueue(" World");
controller.close();
},
});
// URL
const url = new URL("/api/users", "https://example.com");
// TextEncoder/TextDecoder
const encoder = new TextEncoder();
const bytes = encoder.encode("Hello Deno");
// WebSocket
const ws = new WebSocket("wss://echo.websocket.org");
ws.onmessage = (e) => console.log(e.data);
// setTimeout, setInterval, queueMicrotask
setTimeout(() => console.log("delayed"), 1000);
// structuredClone
const clone = structuredClone({ nested: { data: [1, 2, 3] } });
// AbortController
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
await fetch("https://slow.api/data", { signal: controller.signal });
// FormData, Headers, Request, Response
const formData = new FormData();
formData.append("file", new Blob(["content"]), "file.txt");
// Performance API
const start = performance.now();
// ... work ...
console.log(`Took ${performance.now() - start}ms`);
4.8 Deno-Specific APIs
Beyond Web APIs, Deno provides runtime-specific APIs under the Deno namespace:
// File system
const content = await Deno.readTextFile("data.json");
await Deno.writeTextFile("output.txt", "Hello");
const stat = await Deno.stat("file.ts");
for await (const entry of Deno.readDir("./src")) {
console.log(entry.name, entry.isFile);
}
// Environment
const home = Deno.env.get("HOME");
Deno.env.set("MY_VAR", "value");
// Subprocess
const command = new Deno.Command("git", {
args: ["status"],
stdout: "piped",
});
const { stdout, code } = await command.output();
console.log(new TextDecoder().decode(stdout));
// System info
console.log(Deno.hostname());
console.log(Deno.osRelease());
console.log(Deno.build); // { target, arch, os, vendor, env }
// Signals
Deno.addSignalListener("SIGINT", () => {
console.log("Interrupted!");
Deno.exit(0);
});
// Temporary files
const tmpFile = await Deno.makeTempFile({ suffix: ".txt" });
// Network listeners
const listener = Deno.listen({ port: 8080 });
for await (const conn of listener) {
// handle connection
}
4.9 HTTP Server with deno serve
The deno serve command is the built-in way to run HTTP servers:
// server.ts
export default {
fetch(request: Request): Response {
const url = new URL(request.url);
if (url.pathname === "/") {
return new Response("Hello World!");
}
if (url.pathname === "/json") {
return Response.json({ time: new Date().toISOString() });
}
return new Response("Not Found", { status: 404 });
},
};
deno serve --port=8000 server.ts
deno serve: Listening on http://0.0.0.0:8000/
With parallel workers for production:
deno serve --parallel --port=8000 server.ts
This spawns multiple worker threads to handle requests concurrently using all CPU cores.
Tip
deno serveautomatically handles graceful shutdown on SIGINT/SIGTERM, making it ideal for containerized deployments. You can set the address and port via theDENO_SERVE_ADDRESSenvironment variable instead of CLI flags.
4.10 The REPL
Start an interactive TypeScript REPL:
deno repl
Deno 2.7.12
exit using ctrl+d, ctrl+c, or close()
> const x: number = 42;
undefined
> x * 2
84
> await fetch("https://api.github.com/zen").then(r => r.text())
"Anything added dilutes everything else."
>
REPL features:
- TypeScript support with type checking
- Top-level
await - Tab completion
- Multi-line input (auto-detected)
_holds the last evaluated expression_errorholds the last thrown error.exitorCtrl+Dto quit
Import modules in the REPL:
> import { join } from "jsr:@std/path";
undefined
> join("foo", "bar", "baz")
"foo/bar/baz"
Evaluate from a file and drop into REPL:
deno repl --eval-file=setup.ts
5. Common Tasks
5.1 Testing
Writing Tests
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
export function divide(a: number, b: number): number {
if (b === 0) throw new Error("Division by zero");
return a / b;
}
// math_test.ts
import { assertEquals, assertThrows } from "@std/assert";
import { add, divide } from "./math.ts";
Deno.test("add - positive numbers", () => {
assertEquals(add(2, 3), 5);
});
Deno.test("add - negative numbers", () => {
assertEquals(add(-1, -1), -2);
});
Deno.test("divide - normal division", () => {
assertEquals(divide(10, 2), 5);
});
Deno.test("divide - throws on zero", () => {
assertThrows(
() => divide(1, 0),
Error,
"Division by zero",
);
});
deno test
running 4 tests from ./math_test.ts
add - positive numbers ... ok (1ms)
add - negative numbers ... ok (0ms)
divide - normal division ... ok (0ms)
divide - throws on zero ... ok (0ms)
ok | 4 passed | 0 failed (15ms)
Async Tests
Deno.test("fetch returns data", async () => {
const res = await fetch("https://api.github.com/zen");
assertEquals(res.status, 200);
const text = await res.text();
assert(text.length > 0);
});
Test Filtering
# Run tests matching a pattern
deno test --filter "add"
# Run tests in a specific file
deno test math_test.ts
# Run tests in a directory
deno test tests/
# Run only specific test steps
deno test --filter "/divide/"
Test Steps (Subtests)
Deno.test("database operations", async (t) => {
let db: Database;
await t.step("connect", async () => {
db = await Database.connect("sqlite:memory:");
assert(db);
});
await t.step("create table", async () => {
await db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
});
await t.step("insert record", async () => {
await db.execute("INSERT INTO users (name) VALUES (?)", ["Alice"]);
});
await t.step("query record", async () => {
const rows = await db.query("SELECT name FROM users");
assertEquals(rows[0].name, "Alice");
});
});
Test Permissions
Deno.test({
name: "reads config file",
permissions: { read: ["./config.json"] },
fn: async () => {
const config = await Deno.readTextFile("./config.json");
assert(config.length > 0);
},
});
Parallel Tests
deno test --parallel
This runs test files in parallel across multiple workers. Individual tests within a file still run sequentially unless you use { sanitizeOps: false }.
Test Coverage
# Generate coverage data
deno test --coverage=cov_profile
# View coverage report
deno coverage cov_profile
cover math.ts ... 100.000% (12/12)
cover server.ts ... 85.714% (18/21)
Generate an HTML report:
deno coverage cov_profile --html
Generate an LCOV report (for CI integration):
deno coverage cov_profile --lcov --output=coverage.lcov
Mocking and Spying
import { assertSpyCalls, spy, stub } from "@std/testing/mock";
import { FakeTime } from "@std/testing/time";
Deno.test("spy example", () => {
const log = spy(console, "log");
console.log("hello");
assertSpyCalls(log, 1);
log.restore();
});
Deno.test("stub example", () => {
const randomStub = stub(Math, "random", () => 0.5);
assertEquals(Math.random(), 0.5);
randomStub.restore();
});
Deno.test("fake time", () => {
using time = new FakeTime();
const callback = spy();
setTimeout(callback, 1000);
time.tick(1000);
assertSpyCalls(callback, 1);
});
Snapshot Testing
import { assertSnapshot } from "@std/testing/snapshot";
Deno.test("snapshot test", async (t) => {
const result = generateReport();
await assertSnapshot(t, result);
});
First run creates __snapshots__/filename.snap. Subsequent runs compare against it. Update with --update:
deno test --allow-read --allow-write -- --update
5.2 Formatting
Deno includes a built-in code formatter (based on dprint):
# Format all files
deno fmt
# Check if files are formatted (CI mode)
deno fmt --check
# Format specific files
deno fmt src/main.ts src/utils.ts
# Format stdin
echo "const x=1" | deno fmt -
Checked 42 files
Formatter Options
Configure in deno.json:
{
"fmt": {
"useTabs": false,
"lineWidth": 80,
"indentWidth": 2,
"singleQuote": true,
"proseWrap": "preserve",
"semiColons": true,
"include": ["src/", "tests/"],
"exclude": ["vendor/", "dist/"]
}
}
| Option | Default | Values |
|---|---|---|
useTabs |
false |
true, false |
lineWidth |
80 |
1-160 |
indentWidth |
2 |
1-8 |
singleQuote |
false |
true, false |
proseWrap |
"always" |
"always", "never", "preserve" |
semiColons |
true |
true, false |
Deno also formats Markdown, JSON, and JSONC files by default.
Ignoring Formatting
// deno-fmt-ignore
const matrix = [
1, 0, 0,
0, 1, 0,
0, 0, 1,
];
// deno-fmt-ignore-file (at top of file)
5.3 Linting
# Lint all files
deno lint
# Lint specific files
deno lint src/main.ts
# Output as JSON
deno lint --json
Checked 42 files
(no output means no errors)
When issues are found:
error[no-unused-vars]: `helper` is never used
--> src/main.ts:3:7
|
3 | const helper = () => {};
| ^^^^^^
= hint: If this is intentional, prefix it with an underscore like `_helper`
Found 1 problem
Lint Rules
Configure in deno.json:
{
"lint": {
"include": ["src/"],
"exclude": ["src/generated/"],
"rules": {
"tags": ["recommended"],
"include": [
"ban-untagged-todo",
"no-unused-vars",
"explicit-function-return-type",
"no-await-in-loop"
],
"exclude": ["no-explicit-any"]
}
}
}
Available rule tags: recommended, fresh (stricter set for new projects).
Ignoring Lint Rules
// deno-lint-ignore no-explicit-any
const data: any = JSON.parse(input);
// deno-lint-ignore-file no-explicit-any
// (at top of file — ignores for entire file)
// deno-lint-ignore no-unused-vars no-explicit-any
// (multiple rules on one line)
5.4 Type Checking
# Type-check a file and its dependencies
deno check main.ts
# Type-check all files in the project
deno check **/*.ts
# Type-check with remote modules
deno check --remote main.ts
Check file:///home/user/project/main.ts
error: TS2345 [ERROR]: Argument of type 'string' is not assignable to parameter of type 'number'.
add("hello", 5);
~~~~~~~
at file:///home/user/project/main.ts:8:9
Note
deno rundoes NOT type-check by default (for fast startup). Usedeno checkexplicitly in your CI pipeline, or pass--checktodeno runif you want type checking before execution:deno run --check main.ts.
5.5 Dependency Management
Adding Dependencies
# Add from JSR
deno add jsr:@std/http
deno add jsr:@oak/oak
# Add from npm
deno add npm:express
deno add npm:zod
# Add multiple at once
deno add jsr:@std/path jsr:@std/assert npm:chalk
This updates deno.json:
{
"imports": {
"@oak/oak": "jsr:@oak/oak@^17.1.4",
"@std/assert": "jsr:@std/assert@^1.0.12",
"@std/http": "jsr:@std/http@^1.0.16",
"@std/path": "jsr:@std/path@^1.0.10",
"chalk": "npm:chalk@^5.4.1",
"express": "npm:express@^4.21.2",
"zod": "npm:zod@^3.24.2"
}
}
Removing Dependencies
deno remove express
deno remove @std/http
Installing Dependencies (Cache Warming)
# Cache all dependencies referenced in deno.json
deno install
# Cache dependencies for a specific entry point
deno cache main.ts
# Force re-download
deno cache --reload main.ts
# Reload specific packages
deno cache --reload=jsr:@std/http main.ts
Viewing Dependency Info
deno info main.ts
local: /home/user/project/main.ts
type: TypeScript
emit: /home/user/.cache/deno/gen/file/home/user/project/main.ts.js
dependencies: 7 unique
size: 125.4KB
file:///home/user/project/main.ts (1.2KB)
├── jsr:@std/http@1.0.16/serve (15.3KB)
│ ├── jsr:@std/http@1.0.16/_negotiation (2.1KB)
│ └── jsr:@std/http@1.0.16/status (0.8KB)
└── file:///home/user/project/utils.ts (0.5KB)
Auditing Dependencies
deno audit
This checks your dependency tree against known vulnerability databases.
Checking for Outdated Dependencies
deno outdated
Package Current Latest
@std/http 1.0.14 1.0.16
npm:express 4.18.2 4.21.2
npm:zod 3.22.4 3.24.2
Update all dependencies:
deno outdated --update
5.6 Benchmarking
// bench_example.ts
Deno.bench("URL parsing", () => {
new URL("https://example.com/path?query=1#hash");
});
Deno.bench("JSON parse small", () => {
JSON.parse('{"name":"deno","version":"2.7.12"}');
});
Deno.bench("JSON parse large", () => {
JSON.parse(JSON.stringify(Array.from({ length: 1000 }, (_, i) => i)));
});
Deno.bench({
name: "async file read",
permissions: { read: true },
fn: async () => {
await Deno.readTextFile("bench_example.ts");
},
});
deno bench
cpu: AMD Ryzen 9 7950X
runtime: deno 2.7.12 (x86_64-unknown-linux-gnu)
bench_example.ts
benchmark time/iter (avg) iter/s (min … max)
---------------------- ------------------- ------------ -----------------
URL parsing 148.32 ns 6,742,000.0 (142.11 ns … 1.23 µs)
JSON parse small 89.44 ns 11,180,000.0 ( 85.12 ns … 342.1 ns)
JSON parse large 12.15 µs 82,300.0 ( 11.22 µs … 45.3 µs)
async file read 4.22 µs 237,000.0 ( 3.81 µs … 12.1 µs)
Filter benchmarks:
deno bench --filter "JSON"
5.7 Documentation Generation
Generate documentation for your modules:
# View docs in terminal
deno doc mod.ts
# Generate HTML docs
deno doc --html --output=docs mod.ts
# View docs for a remote module
deno doc jsr:@std/http/serve
# Generate JSON output
deno doc --json mod.ts
Write JSDoc comments for your exports:
/**
* Calculates the factorial of a non-negative integer.
*
* @param n - The non-negative integer
* @returns The factorial of n
* @throws {RangeError} If n is negative
*
* @example
* ```ts
* import { factorial } from "./math.ts";
* factorial(5); // 120
* ```
*/
export function factorial(n: number): number {
if (n < 0) throw new RangeError("n must be non-negative");
if (n <= 1) return 1;
return n * factorial(n - 1);
}
5.8 Compiling to Standalone Binaries
# Compile for current platform
deno compile --output=myapp main.ts
# Compile with permissions baked in
deno compile --allow-net --allow-read --output=myapp main.ts
# Cross-compile for different targets
deno compile --target=x86_64-unknown-linux-gnu --output=myapp-linux main.ts
deno compile --target=aarch64-apple-darwin --output=myapp-mac main.ts
deno compile --target=x86_64-pc-windows-msvc --output=myapp.exe main.ts
# Include static files
deno compile --include=static/ --include=templates/ --output=myapp main.ts
Compile file:///home/user/project/main.ts to myapp
Available compilation targets:
| Target | OS | Arch |
|---|---|---|
x86_64-unknown-linux-gnu |
Linux | x86_64 |
aarch64-unknown-linux-gnu |
Linux | ARM64 |
x86_64-pc-windows-msvc |
Windows | x86_64 |
x86_64-apple-darwin |
macOS | Intel |
aarch64-apple-darwin |
macOS | Apple Silicon |
The compiled binary includes the Deno runtime, your code, and all dependencies in a single executable. No Deno installation needed to run it.
Caution
Known issue in Deno 2.7.5+ (issue #32821): compiled binaries may hang on startup. If you encounter this, compile with Deno 2.7.4 as a workaround until the fix lands.
5.9 Publishing to JSR
The JavaScript Registry (JSR) is the native package registry for Deno.
Prepare Your Package
// deno.json
{
"name": "@myorg/mylib",
"version": "1.0.0",
"exports": {
".": "./mod.ts",
"./utils": "./utils.ts"
},
"publish": {
"include": ["src/", "mod.ts", "README.md", "LICENSE"],
"exclude": ["tests/", "bench/"]
}
}
Publish
# Dry run first
deno publish --dry-run
# Publish
deno publish
Publishing @myorg/mylib@1.0.0 ...
Successfully published @myorg/mylib@1.0.0
Version Management
JSR enforces semantic versioning. Increment the version in deno.json before each publish.
5.10 Deploying to Deno Deploy
# Deploy the current project
deno deploy
# Deploy with a specific entry point
deno deploy --entrypoint=main.ts
# Set the project name
deno deploy --project=my-app
# Deploy with environment variables
DENO_DEPLOY_TOKEN=your_token deno deploy --project=my-app
Set the deploy token via environment variable:
export DENO_DEPLOY_TOKEN="ddp_xxxxxxxxxxxxxxxxxxxx"
5.11 Working with JSON
// Read JSON
const config = JSON.parse(await Deno.readTextFile("config.json"));
// Write JSON (formatted)
await Deno.writeTextFile(
"output.json",
JSON.stringify(data, null, 2),
);
// JSON imports (with assertion)
import config from "./config.json" with { type: "json" };
console.log(config.database.host);
5.12 Working with Streams
// Read a file as a stream
const file = await Deno.open("large_file.txt");
const reader = file.readable.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(`Read ${value.byteLength} bytes`);
}
// Pipe a download to a file
const response = await fetch("https://example.com/large-file.zip");
const outputFile = await Deno.open("download.zip", { write: true, create: true });
await response.body!.pipeTo(outputFile.writable);
// Transform streams
const textStream = new ReadableStream({
start(controller) {
controller.enqueue("hello world");
controller.close();
},
}).pipeThrough(new TextEncoderStream());
5.13 Subprocess Management
// Simple command
const command = new Deno.Command("echo", {
args: ["Hello from subprocess"],
});
const { code, stdout, stderr } = await command.output();
console.log(new TextDecoder().decode(stdout)); // "Hello from subprocess\n"
// Streaming output
const process = new Deno.Command("ping", {
args: ["-c", "3", "google.com"],
stdout: "piped",
}).spawn();
for await (const chunk of process.stdout) {
console.log(new TextDecoder().decode(chunk));
}
const status = await process.status;
// Piping between processes
const lsCmd = new Deno.Command("ls", {
args: ["-la"],
stdout: "piped",
});
const grepCmd = new Deno.Command("grep", {
args: [".ts"],
stdin: "piped",
stdout: "piped",
});
const ls = lsCmd.spawn();
const grep = grepCmd.spawn();
ls.stdout.pipeTo(grep.stdin);
const result = await grep.output();
console.log(new TextDecoder().decode(result.stdout));
5.14 Workers (Multi-Threading)
// main.ts
const worker = new Worker(
new URL("./worker.ts", import.meta.url).href,
{ type: "module" },
);
worker.postMessage({ numbers: [1, 2, 3, 4, 5] });
worker.onmessage = (event) => {
console.log("Sum:", event.data.sum); // Sum: 15
worker.terminate();
};
// worker.ts
self.onmessage = (event) => {
const { numbers } = event.data;
const sum = numbers.reduce((a: number, b: number) => a + b, 0);
self.postMessage({ sum });
};
5.15 Foreign Function Interface (FFI)
Call native libraries from Deno:
const lib = Deno.dlopen("libm.so.6", {
cos: { parameters: ["f64"], result: "f64" },
sin: { parameters: ["f64"], result: "f64" },
});
console.log(lib.symbols.cos(0)); // 1.0
console.log(lib.symbols.sin(0)); // 0.0
lib.close();
deno run --allow-ffi --unstable-ffi main.ts
5.16 WebSocket Server
// ws_server.ts
Deno.serve({ port: 8080 }, (request) => {
if (request.headers.get("upgrade") !== "websocket") {
return new Response("Expected WebSocket", { status: 400 });
}
const { socket, response } = Deno.upgradeWebSocket(request);
socket.onopen = () => console.log("Client connected");
socket.onmessage = (event) => {
console.log("Received:", event.data);
socket.send(`Echo: ${event.data}`);
};
socket.onclose = () => console.log("Client disconnected");
return response;
});
console.log("WebSocket server running on ws://localhost:8080");
deno run --allow-net ws_server.ts
5.17 File Server (One-Liner)
Serve the current directory as static files:
deno run --allow-net --allow-read jsr:@std/http/file-server
Listening on http://0.0.0.0:4507/
With options:
deno run --allow-net --allow-read jsr:@std/http/file-server --port=3000 --dir-listing
5.18 Cron / Scheduled Tasks (Unstable)
// Enable in deno.json: "unstable": ["cron"]
Deno.cron("daily-report", "0 9 * * *", async () => {
console.log("Generating daily report...");
await generateReport();
});
Deno.cron("cleanup", "*/5 * * * *", async () => {
console.log("Running cleanup every 5 minutes");
await cleanup();
});
5.19 Deno KV (Key-Value Store, Unstable)
// Enable in deno.json: "unstable": ["kv"]
const kv = await Deno.openKv();
// Set a value
await kv.set(["users", "alice"], { name: "Alice", age: 30 });
// Get a value
const result = await kv.get(["users", "alice"]);
console.log(result.value); // { name: "Alice", age: 30 }
// List entries by prefix
const entries = kv.list({ prefix: ["users"] });
for await (const entry of entries) {
console.log(entry.key, entry.value);
}
// Atomic transactions
const res = await kv.atomic()
.check({ key: ["users", "alice"], versionstamp: result.versionstamp })
.set(["users", "alice"], { name: "Alice", age: 31 })
.commit();
if (res.ok) {
console.log("Updated successfully");
}
// Delete
await kv.delete(["users", "alice"]);
kv.close();
5.20 Jupyter Notebook Integration
Deno can serve as a Jupyter kernel:
deno jupyter --install
✅ Deno kernel successfully registered
Then create a new notebook in JupyterLab and select the "Deno" kernel. You get full TypeScript support, top-level await, and access to all Deno APIs in notebook cells.
5.21 Building a REST API (Full Example)
// api.ts
interface Todo {
id: number;
title: string;
completed: boolean;
}
const todos: Todo[] = [
{ id: 1, title: "Learn Deno", completed: true },
{ id: 2, title: "Build an API", completed: false },
];
let nextId = 3;
function jsonResponse(data: unknown, status = 200): Response {
return new Response(JSON.stringify(data), {
status,
headers: { "Content-Type": "application/json" },
});
}
export default {
fetch(request: Request): Response {
const url = new URL(request.url);
const path = url.pathname;
// GET /todos
if (request.method === "GET" && path === "/todos") {
return jsonResponse(todos);
}
// GET /todos/:id
const match = path.match(/^\/todos\/(\d+)$/);
if (request.method === "GET" && match) {
const todo = todos.find((t) => t.id === Number(match[1]));
if (!todo) return jsonResponse({ error: "Not found" }, 404);
return jsonResponse(todo);
}
// POST /todos
if (request.method === "POST" && path === "/todos") {
return (async () => {
const body = await request.json();
const todo: Todo = {
id: nextId++,
title: body.title,
completed: false,
};
todos.push(todo);
return jsonResponse(todo, 201);
})();
}
return jsonResponse({ error: "Not found" }, 404);
},
};
deno serve --port=8000 api.ts
Test it:
# List todos
curl http://localhost:8000/todos
# Get one todo
curl http://localhost:8000/todos/1
# Create a todo
curl -X POST http://localhost:8000/todos -H "Content-Type: application/json" -d '{"title":"Deploy to production"}'
5.22 Database Access
SQLite
const db = new Deno.openKv(":memory:"); // KV as embedded store
// Or use the community SQLite driver:
import { DB } from "https://deno.land/x/sqlite/mod.ts";
const db = new DB("app.db");
db.execute(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE
)
`);
db.query("INSERT INTO users (name, email) VALUES (?, ?)", ["Alice", "[email removed]"]);
for (const [id, name, email] of db.query("SELECT * FROM users")) {
console.log(id, name, email);
}
db.close();
PostgreSQL
import postgres from "npm:postgres@^3";
const sql = postgres("postgresql://user:pass@localhost:5432/mydb");
const users = await sql`SELECT * FROM users WHERE active = ${true}`;
console.log(users);
await sql.end();
5.23 Environment Variables
// Read
const dbUrl = Deno.env.get("DATABASE_URL");
if (!dbUrl) {
console.error("DATABASE_URL is required");
Deno.exit(1);
}
// Set (within the process)
Deno.env.set("NODE_ENV", "production");
// List all
for (const [key, value] of Object.entries(Deno.env.toObject())) {
console.log(`${key}=${value}`);
}
Load .env files:
import "@std/dotenv/load";
// or
import { load } from "@std/dotenv";
const env = await load();
deno run --allow-env --allow-read main.ts
5.24 Signals and Graceful Shutdown
const server = Deno.serve({ port: 8000 }, (_req) => {
return new Response("Hello");
});
// Handle SIGINT (Ctrl+C)
Deno.addSignalListener("SIGINT", () => {
console.log("\nGracefully shutting down...");
server.shutdown();
});
// Handle SIGTERM (from container orchestrators)
Deno.addSignalListener("SIGTERM", () => {
console.log("SIGTERM received, shutting down...");
server.shutdown();
});
console.log("Server running on http://localhost:8000");
await server.finished;
console.log("Server shut down cleanly.");
5.25 OpenTelemetry Integration
Deno has built-in OpenTelemetry support for traces and metrics:
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
OTEL_SERVICE_NAME=my-deno-app \
deno run --allow-net --allow-env --unstable-otel main.ts
Traces from fetch(), Deno.serve(), and other built-in APIs are emitted automatically when OTEL is configured.
5.26 Building a CLI Tool
// cli.ts
import { parseArgs } from "jsr:@std/cli/parse-args";
const args = parseArgs(Deno.args, {
string: ["name", "output"],
boolean: ["verbose", "help"],
alias: { n: "name", o: "output", v: "verbose", h: "help" },
default: { verbose: false },
});
if (args.help) {
console.log(`
Usage: greet [options]
Options:
-n, --name <name> Name to greet
-o, --output <file> Output file
-v, --verbose Verbose output
-h, --help Show help
`);
Deno.exit(0);
}
const greeting = `Hello, ${args.name ?? "World"}!`;
if (args.verbose) {
console.log(`Generating greeting for: ${args.name}`);
}
if (args.output) {
await Deno.writeTextFile(args.output, greeting);
console.log(`Written to ${args.output}`);
} else {
console.log(greeting);
}
Install globally:
deno install --global --allow-read --allow-write -n greet cli.ts
greet --name "Deno User"
Hello, Deno User!
5.27 Testing HTTP Handlers
// handler.ts
export function handler(request: Request): Response {
const url = new URL(request.url);
if (url.pathname === "/health") {
return new Response("OK", { status: 200 });
}
return new Response("Not Found", { status: 404 });
}
// handler_test.ts
import { assertEquals } from "@std/assert";
import { handler } from "./handler.ts";
Deno.test("health check returns 200", async () => {
const req = new Request("http://localhost/health");
const res = handler(req);
assertEquals(res.status, 200);
assertEquals(await res.text(), "OK");
});
Deno.test("unknown route returns 404", async () => {
const req = new Request("http://localhost/unknown");
const res = handler(req);
assertEquals(res.status, 404);
});
5.28 Bundling
deno bundle main.ts output.js
Note
deno bundleis considered legacy. For production builds, preferdeno compilefor standalone binaries or use a build tool like esbuild (npm:esbuild) for web bundles. The bundler may be removed in a future version.
5.29 Vendoring Dependencies
Download all remote dependencies into a local vendor/ directory:
deno vendor main.ts
This creates a vendor/ directory with all remote modules and an import map. Useful for:
- Air-gapped environments
- Auditing dependencies
- Checking dependencies into version control
5.30 Global Installs and Uninstalls
# Install a script globally
deno install --global --allow-net -n serve jsr:@std/http/file-server
# List global installs
ls ~/.deno/bin/
# Uninstall
deno uninstall --global serve
5.31 LSP (Language Server Protocol)
Deno ships a built-in LSP for editor integration:
deno lsp
VS Code Setup
Install the official "Deno" extension (denoland.vscode-deno). Enable it in .vscode/settings.json:
{
"deno.enable": true,
"deno.lint": true,
"deno.unstable": ["kv"],
"editor.defaultFormatter": "denoland.vscode-deno",
"[typescript]": {
"editor.defaultFormatter": "denoland.vscode-deno"
}
}
Features: auto-completion, go-to-definition, hover types, inline diagnostics, code actions, formatting, import suggestions, test code lens.
Warning
Known issue (#33289): the LSP may not recognize globally declared types (ambient declarations) unless the declaring file is currently open in the editor. Workaround: add the declaration file to a
/// <reference path="..." />directive in the affected file.
5.32 Printing TypeScript Declarations
deno types
Outputs the built-in TypeScript declaration file for all Deno APIs. Pipe it to a file for offline reference:
deno types > deno.d.ts
6. Troubleshooting
6.1 Permission Errors
Error: Requires read access to "/path/to/file", run again with the --allow-read flag
Cause: Your script tried to read a file but you did not grant --allow-read.
Solution:
# Allow reading a specific path
deno run --allow-read=/path/to/file main.ts
# Allow reading the current directory and below
deno run --allow-read=. main.ts
Prevention: Plan your permission flags before running. Use deno task entries with permissions pre-configured so you do not have to remember them.
Error: Requires net access to "api.example.com:443", run again with the --allow-net flag
Cause: Your script made a network request without the --allow-net permission.
Solution:
deno run --allow-net=api.example.com main.ts
Prevention: Scope network permissions to specific hosts in production: --allow-net=api.example.com,db.example.com.
Error: Requires env access to "DATABASE_URL", run again with the --allow-env flag
Cause: Your script tried to read an environment variable without --allow-env.
Solution:
deno run --allow-env=DATABASE_URL main.ts
Error: Requires run access to "git", run again with the --allow-run flag
Cause: Your script tried to spawn a subprocess without --allow-run.
Solution:
deno run --allow-run=git main.ts
Caution
Be very careful with
--allow-runwithout a subprocess allow-list. An unrestricted--allow-runlets the script execute any program on the system, effectively bypassing all other permissions.
6.2 Module Resolution Errors
Error: Module not found "file:///path/to/module.ts"
Cause: The imported file does not exist at the specified path or you forgot the file extension.
Solution: Deno requires explicit file extensions for local imports:
// Wrong
import { foo } from "./module";
// Correct
import { foo } from "./module.ts";
Error: error: Uncaught TypeError: Cannot find package 'some-package'
Cause: You imported an npm package without the npm: prefix.
Solution:
// Wrong
import express from "express";
// Correct (direct import)
import express from "npm:express";
// Correct (via import map in deno.json)
// deno.json: { "imports": { "express": "npm:express@^4" } }
import express from "express";
Error: The source code is invalid, as it does not match the expected hash in the lock file
Cause: A remote dependency changed since the lock file was generated.
Solution:
# Verify the change is expected, then update the lock file
deno cache --lock=deno.lock --lock-write main.ts
Warning
Investigate why the hash changed before updating. A hash mismatch could indicate a supply-chain attack. Compare the old and new versions of the module before accepting the change.
Error: error: Unsupported scheme "node" or error: Module not found "node:fs"
Cause: This typically happens on very old Deno versions (pre-1.x). On Deno 2.x, node: imports work out of the box.
Solution: Upgrade Deno:
deno upgrade
6.3 TypeScript Errors
Error: TS2307 [ERROR]: Cannot find module '@std/path' or its corresponding type declarations
Cause: The import is not in your deno.json imports map, or the package is not cached.
Solution:
deno add jsr:@std/path
deno cache main.ts
Error: TS2345 [ERROR]: Argument of type 'X' is not assignable to parameter of type 'Y'
Cause: Type mismatch in your code. Deno uses strict: true by default.
Solution: Fix the type error in your code, or if you need to bypass temporarily:
// @ts-ignore: reason for ignoring
someFunction(value as any);
Error: TS2304 [ERROR]: Cannot find name 'Deno'
Cause: The lib compiler option does not include Deno's type declarations.
Solution: In deno.json:
{
"compilerOptions": {
"lib": ["deno.window"]
}
}
6.4 Network and TLS Errors
Error: error: error sending request [...] tls handshake eof
Cause: TLS negotiation failed, often due to self-signed certificates or corporate proxies.
Solution:
# Use a custom CA cert
deno run --cert=./ca-cert.pem --allow-net main.ts
# Or set via environment variable
DENO_CERT=/path/to/cert.pem deno run --allow-net main.ts
Error: TLS connection to Postgres via npm:pg@8 times out
Cause: Known issue (#33296). TLS connections via the pg npm package can time out in Deno 2.7.x.
Solution: Try npm:postgres (the pure-JS client) as an alternative:
import postgres from "npm:postgres";
const sql = postgres("postgres://user:pass@host/db");
Error: error: error sending request [...] connection refused
Cause: The target server is not running or is blocked by firewall.
Solution: Verify the server is running and reachable:
curl -v http://localhost:8000
If using Docker, ensure the container port is mapped and the service is bound to 0.0.0.0, not 127.0.0.1.
6.5 Runtime Errors
Error: error: Top-level await is not allowed in the current context
Cause: This happens when using top-level await in a script that Deno treats as a classic script.
Solution: Ensure your file has a .ts, .mts, .js, or .mjs extension. Deno treats these as ES modules automatically.
Error: Uncaught PermissionDenied: [...] not in the allow list
Cause: You provided a scoped permission but the code is accessing something outside the scope.
Solution: Widen the permission scope or add the missing path/host:
# Before (too narrow)
deno run --allow-read=./src main.ts
# After (include config directory too)
deno run --allow-read=./src,./config main.ts
Error: error: Worker terminated
Cause: A Web Worker crashed or was terminated unexpectedly.
Solution: Wrap worker code in try/catch and check for errors:
worker.onerror = (event) => {
console.error("Worker error:", event.message);
};
Error: Panic: [...] assertion failed
Cause: An internal Deno bug. Panics are Rust-level crashes.
Solution: Report the issue at github.com/denoland/deno/issues with the full panic trace. Check if a newer version includes a fix:
deno upgrade
Known panic: running Task fmt prettier --write . && deno fmt can trigger a panic (#33302). Run prettier and deno fmt as separate tasks instead.
Caution
If you encounter a panic consistently, check the Deno issues page for existing reports before filing a new one. Include your OS, Deno version (
deno --version), and the minimal reproduction steps.
6.6 Compilation Issues
Error: Compiled binary hangs on startup
Cause: Known regression in Deno 2.7.5+ (#32821).
Solution: Downgrade to Deno 2.7.4 for compilation:
deno upgrade --version 2.7.4
deno compile --output=myapp main.ts
deno upgrade # Go back to latest for development
Error: error: Could not find constraint in the list of versions
Cause: A dependency version constraint cannot be satisfied.
Solution: Check for typos in your version specifier and ensure the package exists:
deno info npm:package-name
6.7 Formatter and Linter Issues
Error: error: Failed to format or formatter produces unexpected output
Cause: A syntax error in the file or unsupported language construct.
Solution: Fix the syntax error first. Run deno check to find errors, then format.
Error: Not implemented: [feature] from lint rules
Cause: Some lint rules are not fully implemented for all code patterns.
Solution: Disable the specific rule for that line:
// deno-lint-ignore rule-name
6.8 Performance Issues
Problem: Slow startup time
Cause: Large dependency trees or uncached remote modules.
Solution:
# Pre-cache all dependencies
deno cache main.ts
# Skip type checking for faster startup
deno run main.ts # (no --check flag)
Problem: High memory usage
Cause: V8's default heap limit or large data processing.
Solution: Increase V8's heap:
DENO_V8_FLAGS="--max-old-space-size=8192" deno run main.ts
Problem: Tests run slowly
Cause: Tests running sequentially, or each test doing expensive setup.
Solution:
# Run test files in parallel
deno test --parallel
# Set parallel job count
DENO_JOBS=8 deno test --parallel
6.9 Platform-Specific Issues
Error: node:fs writeFileSync throws EEXIST when overwriting (Windows)
Cause: Known Windows-specific issue (#33254) where writeFileSync throws EEXIST instead of overwriting.
Solution: Use the async writeFile or add { flag: "w" }:
// Workaround
import { writeFileSync } from "node:fs";
try {
writeFileSync(path, data);
} catch (e) {
if (e.code === "EEXIST") {
// Delete and retry on Windows
Deno.removeSync(path);
writeFileSync(path, data);
}
}
// Better: use Deno's native API
await Deno.writeTextFile(path, data);
Error: Firefox launch with Playwright times out
Cause: Known issue (#33229) with Playwright's Firefox launcher in Deno.
Solution: Use Chromium with Playwright instead:
import { chromium } from "npm:playwright";
const browser = await chromium.launch();
Error: crypto.verify throws Invalid PEM error
Cause: Known issue (#33291) with certain PEM-encoded keys.
Solution: Ensure the PEM key has proper line breaks and headers. As a workaround, import the key as raw bytes:
const keyData = await Deno.readFile("key.der");
const key = await crypto.subtle.importKey(
"pkcs8",
keyData,
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
false,
["sign"],
);
6.10 Cache Issues
Problem: Stale cached modules
Cause: Deno caches remote modules aggressively.
Solution:
# Reload all remote modules
deno cache --reload main.ts
# Reload specific modules
deno cache --reload=https://deno.land main.ts
deno cache --reload=npm:express main.ts
Problem: Cache directory growing too large
Cause: Accumulated cached modules over time.
Solution: Clear the cache:
# Find your cache directory
deno info
# The "DENO_DIR" line shows the cache location
# Clear it manually
rm -rf ~/.cache/deno # Linux
rm -rf ~/Library/Caches/deno # macOS
Or set a custom cache directory:
export DENO_DIR=/tmp/deno-cache
6.11 Source Map Issues
Error: Inline source maps from TypeScript not applied
Cause: Known issue (#32355) where inline source maps in transformed TypeScript are not properly applied.
Solution: Use external source map files or rely on Deno's built-in TypeScript support (which does not require source maps for .ts files).
6.12 Test Runner Issues
Error: Test Explorer environment file loading diverges from CLI
Cause: Known issue (#33284). The VS Code Test Explorer loads .env files differently than the CLI test runner.
Solution: Pass environment variables explicitly or use @std/dotenv/load in a test setup file rather than relying on automatic .env loading.
Error: no output recorded in VS Code test run
Cause: Known issue (#28797) with the VS Code Deno extension test integration.
Solution: Run tests from the terminal instead:
deno test --allow-read
Warning
When switching between the VS Code Test Explorer and CLI test execution, be aware that permission handling, environment variable loading, and output capture may differ. The CLI runner (
deno test) is the authoritative test runner.
7. FAQ
General
Q: What is the difference between Deno and Node.js? A: Deno is secure by default (sandboxed), supports TypeScript natively (no build step), uses ES modules exclusively (no CommonJS), includes a complete toolchain (formatter, linter, test runner), and has built-in Web API compatibility. Node.js has a larger ecosystem and longer track record.
Q: Can I use my existing Node.js code in Deno?
A: Most Node.js code works with minimal changes. Use node: prefixed imports for built-in modules (import fs from "node:fs") and npm: specifiers for packages (import express from "npm:express"). CommonJS require() is available via createRequire() from node:module.
Q: Is Deno production-ready?
A: Yes. Deno 2.x is a stable release with SemVer guarantees. Companies including Netlify, Slack, and GitHub use Deno in production. The stable APIs (non-unstable flagged) follow backward-compatibility rules.
Q: What license is Deno? A: MIT license.
Q: How large is the Deno binary? A: Approximately 80-130 MB depending on the platform. It bundles V8, the TypeScript compiler, and the full toolchain.
Q: Does Deno support Windows? A: Yes. Deno supports Windows 10+ on x86_64. Some features have platform-specific behaviors (see the Windows-specific issues in the Troubleshooting section).
TypeScript
Q: Do I need to install TypeScript separately?
A: No. Deno includes the TypeScript compiler. Just write .ts files and run them directly.
Q: What TypeScript version does Deno 2.7.12 use? A: TypeScript 5.7.3.
Q: Can I use a tsconfig.json?
A: Not directly. Deno uses the compilerOptions field in deno.json. Most tsconfig.json options have equivalents.
Q: Does Deno type-check when I run deno run?
A: No. deno run skips type checking for faster startup. Use deno check for type checking, or deno run --check to check before running.
Q: How do I use JSX/TSX?
A: Add JSX configuration to deno.json:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react"
}
}
Modules and Dependencies
Q: Where does Deno cache modules?
A: In $DENO_DIR (defaults to ~/.cache/deno on Linux, ~/Library/Caches/deno on macOS, %LOCALAPPDATA%\deno on Windows). Run deno info to see the exact path.
Q: How do I use private npm packages?
A: Deno reads .npmrc for registry configuration, including authentication tokens for private registries.
Q: What is JSR and how does it differ from npm?
A: JSR (JavaScript Registry) is a new package registry designed for Deno and modern runtimes. It natively supports TypeScript, ES modules, and semantic versioning. npm packages work in Deno via the npm: specifier, but JSR packages are optimized for Deno's module system.
Q: Can I use import maps?
A: Yes. The "imports" field in deno.json IS the import map. Deno also supports standalone import map files with --import-map=import_map.json.
Q: How do I pin dependency versions?
A: Use deno.lock (generated automatically) for hash-level pinning. Use exact versions in your import map for version pinning: "@std/http": "jsr:@std/http@1.0.16" (no ^ or ~).
Q: What is the vendor/ directory for?
A: Running deno vendor main.ts downloads all remote dependencies into vendor/. Useful for air-gapped environments, auditing, or checking dependencies into version control.
Permissions and Security
Q: Why does Deno need permission flags? A: Security. Deno sandboxes all scripts by default. No file, network, environment, or subprocess access unless explicitly granted. This prevents malicious or buggy code from harming your system.
Q: Can a dependency request permissions that my main script does not have?
A: No. Permissions are granted at the process level. If you run deno run --allow-net=api.github.com main.ts, then all code in the process (including dependencies) can only access api.github.com. No dependency can escalate beyond what you granted.
Q: What does -A / --allow-all do exactly?
A: It grants all permissions: read, write, net, env, run, sys, ffi. The sandbox is effectively disabled. Use only for trusted code during development.
Q: Can deny flags override --allow-all?
A: No. --allow-all takes absolute precedence. Deny flags only work when used alongside specific allow flags.
Configuration
Q: Is deno.json required?
A: No. Deno works without any configuration file. All defaults are sensible. The config file is for customizing behavior, defining tasks, and setting up import maps.
Q: Can I use deno.jsonc instead of deno.json?
A: Yes. Deno supports both. The .jsonc extension allows comments and trailing commas.
Q: How do I enable unstable APIs?
A: In deno.json:
{
"unstable": ["kv", "cron", "temporal", "ffi"]
}
Or via CLI: deno run --unstable-kv --unstable-cron main.ts.
Q: What is nodeModulesDir and when should I use it?
A: Setting "nodeModulesDir": "auto" in deno.json makes Deno create a local node_modules directory. Use it when tools (bundlers, editors, etc.) expect node_modules to exist.
Tooling
Q: Can I use Deno's formatter for non-TypeScript files?
A: Yes. deno fmt formats TypeScript, JavaScript, JSON, JSONC, and Markdown files.
Q: How do I integrate Deno with CI/CD? A: A typical CI pipeline:
# .github/workflows/ci.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: denoland/setup-deno@v2
with:
deno-version: v2.7.12
- run: deno lint
- run: deno fmt --check
- run: deno check main.ts
- run: deno test --allow-read --parallel
Q: Does Deno have a package.json equivalent?
A: deno.json serves a similar role. Deno also reads package.json for backward compatibility when present.
Q: Can I debug Deno programs?
A: Yes. Use --inspect or --inspect-brk:
deno run --inspect-brk main.ts
Then open chrome://inspect in Chrome or attach from VS Code.
Q: How do I profile Deno performance? A: Use V8's built-in profiler:
deno run --v8-flags=--prof main.ts
# Process the generated .log file
deno run --v8-flags=--prof-process isolate-*.log
Deployment
Q: How do I deploy Deno to production?
A: Several options: (1) deno compile to a standalone binary, (2) Docker using denoland/deno as the base image, (3) Deno Deploy for serverless, (4) any Linux server with Deno installed.
Q: What is Deno Deploy?
A: Deno Deploy is a serverless edge platform by the Deno team. Deploy directly with deno deploy or connect a GitHub repository for automatic deployments.
Q: Can I use Deno in AWS Lambda? A: Yes, via a custom runtime layer or by compiling to a standalone binary and using the provided.al2 runtime.
Q: What is the minimum Docker image size for Deno?
A: The denoland/deno:alpine image is approximately 140 MB. For the smallest image, use deno compile and copy only the binary into a FROM scratch or FROM alpine image.
Compatibility
Q: Does Deno support Web Workers?
A: Yes. Use the standard Worker API with { type: "module" }.
Q: Does Deno support WebAssembly?
A: Yes. Both WebAssembly.instantiate() and .wasm file imports work.
Q: Can Deno call C/C++/Rust libraries?
A: Yes, via the Foreign Function Interface (Deno.dlopen()). Requires --allow-ffi and --unstable-ffi.
Q: Does Deno support HTTP/2?
A: Yes. Both the node:http2 polyfill and Deno's native server support HTTP/2.
Q: What about WASI (WebAssembly System Interface)?
A: Deno includes a node:wasi polyfill. Native WASI support is available via the --unstable-wasi flag.
Q: How does Deno handle __dirname and __filename?
A: These Node.js globals do not exist in Deno. Use:
const __filename = new URL(import.meta.url).pathname;
const __dirname = new URL(".", import.meta.url).pathname;
// Or on all platforms:
import { fromFileUrl, dirname } from "@std/path";
const __filename = fromFileUrl(import.meta.url);
const __dirname = dirname(__filename);
Environment Variable Reference
| Variable | Purpose | Default |
|---|---|---|
DENO_DIR |
Cache directory for remote modules | ~/.cache/deno (Linux), ~/Library/Caches/deno (macOS), %LOCALAPPDATA%\deno (Windows) |
DENO_V8_FLAGS |
V8 engine flags (e.g., --max-old-space-size=4096) |
(none) |
DENO_JOBS |
Number of parallel jobs for deno test --parallel |
CPU count |
DENO_CERT |
Path to a PEM certificate for TLS | (none) |
DENO_DEPLOY_TOKEN |
Authentication token for Deno Deploy | (none) |
DENO_SERVE_ADDRESS |
Default address for deno serve |
0.0.0.0:8000 |
DENO_AUTO_SERVE |
Enable auto-serve mode | (none) |
DENO_CANARY |
Use canary channel for deno upgrade |
(none) |
DENO_WEBGPU_BACKEND |
WebGPU backend selection | (auto) |
DENO_KV_ACCESS_TOKEN |
KV access token for remote KV stores | (none) |
DENO_AUDIT_PERMISSIONS |
Enable permission audit logging | (none) |
DENO_CONDITIONS |
Custom module resolution conditions | (none) |
DENO_UNSTABLE_* |
Enable specific unstable features | (none) |
NO_COLOR |
Disable colored terminal output (any value) | (none) |
HTTP_PROXY |
HTTP proxy URL | (none) |
HTTPS_PROXY |
HTTPS proxy URL | (none) |
NO_PROXY |
Comma-separated hosts to bypass proxy | (none) |
NODE_OPTIONS |
Node.js-compatible runtime options | (none) |
NODE_PATH |
Additional Node module resolution paths | (none) |
OTEL_EXPORTER_OTLP_ENDPOINT |
OpenTelemetry OTLP endpoint URL | (none) |
OTEL_EXPORTER_OTLP_PROTOCOL |
OTLP protocol (grpc, http/protobuf) |
(none) |
OTEL_SERVICE_NAME |
Service name for OpenTelemetry traces | (none) |
CLI Command Reference
| Command | Purpose | Key Flags |
|---|---|---|
deno run <script> |
Run a script | --watch, --check, --inspect, -A |
deno test |
Run tests | --parallel, --filter, --coverage, --doc |
deno bench |
Run benchmarks | --filter, --json |
deno fmt |
Format code | --check, --ext, --single-quote |
deno lint |
Lint code | --json, --rules |
deno check |
Type-check | --remote, --all |
deno doc |
Generate documentation | --html, --json, --filter |
deno coverage <dir> |
View coverage report | --lcov, --html, --output |
deno compile |
Compile to binary | --target, --output, --include |
deno bundle |
Bundle modules (legacy) | --watch |
deno task <name> |
Run a task from deno.json | (none) |
deno install |
Install dependencies | --global, --name |
deno add <pkg> |
Add a dependency | (none) |
deno remove <pkg> |
Remove a dependency | (none) |
deno audit |
Audit dependencies | (none) |
deno outdated |
Check for outdated deps | --update |
deno repl |
Interactive REPL | --eval, --eval-file |
deno jupyter |
Install Jupyter kernel | --install |
deno publish |
Publish to JSR | --dry-run, --token |
deno deploy |
Deploy to Deno Deploy | --project, --entrypoint |
deno init [dir] |
Initialize a new project | (none) |
deno upgrade |
Upgrade Deno | --version, --canary |
deno uninstall |
Uninstall a global package | --global |
deno serve <script> |
Serve HTTP | --port, --parallel |
deno eval <code> |
Evaluate code | --ext, --print |
deno cache <files> |
Cache dependencies | --reload, --lock |
deno info [file] |
Show dependency tree / cache info | --json |
deno types |
Print TypeScript declarations | (none) |
deno completions <shell> |
Generate shell completions | (none) |
deno lsp |
Start Language Server Protocol | (none) |