Jesse A. Green / notebook

Deno User Guide

Generated May 2026 from denoland/deno at 7529c84

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

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

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" and export 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 main and may contain breaking changes or unfinished features. Do not use canary in production. Set DENO_CANARY=1 in your environment to make deno upgrade default 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.json file. Deno's defaults (strict: true, targeting esnext) are intentionally modern. Only add compilerOptions when 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-all disables 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=/etc and --deny-read=/etc/shadow are set, Deno allows reading everything under /etc except /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", not import { 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 serve automatically handles graceful shutdown on SIGINT/SIGTERM, making it ideal for containerized deployments. You can set the address and port via the DENO_SERVE_ADDRESS environment 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
  • _error holds the last thrown error
  • .exit or Ctrl+D to 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 run does NOT type-check by default (for fast startup). Use deno check explicitly in your CI pipeline, or pass --check to deno run if 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 bundle is considered legacy. For production builds, prefer deno compile for 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-run without a subprocess allow-list. An unrestricted --allow-run lets 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)