Deno
Deno is a secure runtime for JavaScript and TypeScript built on V8 and Rust. Unlike Node.js, Deno is secure by default — scripts run in a sandbox and must be granted explicit permissions to access the filesystem, network, and environment.
Installation
# macOS / Linux (shell installer)
curl -fsSL https://deno.land/install.sh | sh
# macOS (Homebrew)
brew install deno
# Windows (PowerShell)
irm https://deno.land/install.ps1 | iex
# Windows (Scoop)
scoop install deno
# Upgrade existing installation
deno upgrade
# Upgrade to specific version
deno upgrade --version 1.45.0
# Check installed version
deno --version
Add Deno to your shell path after installation:
export DENO_INSTALL="$HOME/.deno"
export PATH="$DENO_INSTALL/bin:$PATH"
Configuration
Deno uses deno.json (or deno.jsonc) for project configuration:
{
"name": "my-app",
"version": "1.0.0",
"tasks": {
"dev": "deno run --watch --allow-net --allow-read src/main.ts",
"build": "deno compile --allow-net --allow-read src/main.ts",
"test": "deno test --allow-read tests/",
"lint": "deno lint",
"fmt": "deno fmt"
},
"imports": {
"@std/http": "jsr:@std/http@^1.0.0",
"@std/path": "jsr:@std/path@^1.0.0",
"hono": "npm:hono@^4.0.0"
},
"lint": {
"include": ["src/", "tests/"],
"exclude": ["dist/"],
"rules": {
"tags": ["recommended"],
"include": ["ban-untagged-todo"],
"exclude": ["no-unused-vars"]
}
},
"fmt": {
"useTabs": false,
"lineWidth": 100,
"indentWidth": 2,
"singleQuote": true,
"include": ["src/", "tests/"]
},
"compilerOptions": {
"strict": true,
"lib": ["deno.window"]
},
"test": {
"include": ["tests/**/*.test.ts"]
}
}
Import Maps
{
"imports": {
"std/": "https://deno.land/std@0.224.0/",
"react": "npm:react@18",
"lodash": "npm:lodash@4"
}
}
Core Commands
| Command | Description |
|---|---|
deno run file.ts | Run a TypeScript/JavaScript file |
deno run --watch file.ts | Run with file watching (auto-restart) |
deno run --allow-all file.ts | Run with all permissions enabled |
deno task dev | Run a task defined in deno.json |
deno fmt | Format source files |
deno fmt --check | Check formatting without modifying files |
deno lint | Lint source files |
deno lint --fix | Auto-fix lint issues |
deno test | Run all test files |
deno test --watch | Run tests in watch mode |
deno test --coverage | Run tests with coverage reporting |
deno compile file.ts | Compile to self-contained executable |
deno bundle file.ts out.js | Bundle to single JS file (deprecated, use esbuild) |
deno install | Install a script as a CLI tool |
deno info file.ts | Show module dependency tree |
deno check file.ts | Type-check without running |
deno doc file.ts | Generate documentation |
deno cache file.ts | Download and cache dependencies |
deno upgrade | Upgrade Deno to latest version |
deno repl | Start interactive REPL |
Permissions Model
| Permission Flag | Description |
|---|---|
--allow-read | Read filesystem access |
--allow-read=/tmp | Read access to specific path |
--allow-write | Write filesystem access |
--allow-write=/tmp | Write access to specific path |
--allow-net | Network access (all hosts) |
--allow-net=example.com | Network access to specific host |
--allow-env | Access environment variables |
--allow-env=HOME,PATH | Access specific env vars |
--allow-run | Spawn subprocesses |
--allow-run=git,npm | Run specific executables |
--allow-sys | Access system info APIs |
--allow-hrtime | High-resolution time access |
--allow-ffi | Load dynamic libraries |
--allow-all | All permissions (use with caution) |
--no-prompt | Fail instead of prompting for permissions |
--deny-net | Explicitly deny network access |
Advanced Usage
HTTP Server
import { Hono } from "npm:hono@4";
const app = new Hono();
app.get("/", (c) => c.json({ message: "Hello from Deno!" }));
app.get("/users/:id", (c) => {
const id = c.req.param("id");
return c.json({ id, name: "Alice" });
});
Deno.serve({ port: 8000 }, app.fetch);
Deno KV Storage
// Open the default KV store
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 with prefix
for await (const entry of kv.list({ prefix: ["users"] })) {
console.log(entry.key, entry.value);
}
// Atomic transactions
await kv.atomic()
.check({ key: ["counter"], versionstamp: null })
.set(["counter"], 0)
.commit();
// Watch for changes
const stream = kv.watch([["counter"]]);
for await (const [entry] of stream) {
console.log("Counter changed:", entry.value);
}
Compile to Binary
# Compile for current platform
deno compile --allow-net --allow-read src/main.ts -o myapp
# Cross-compile for different targets
deno compile --target x86_64-unknown-linux-gnu src/main.ts
deno compile --target x86_64-pc-windows-msvc src/main.ts
deno compile --target aarch64-apple-darwin src/main.ts
# Include assets in the binary
deno compile --include assets/ src/main.ts
Working with npm Packages
// Direct npm imports (no install needed)
import express from "npm:express@4";
import { z } from "npm:zod@3";
// Using JSR (JavaScript Registry)
import { assertEquals } from "jsr:@std/assert@1";
// Node.js built-ins via node: specifier
import { readFileSync } from "node:fs";
import path from "node:path";
Testing
import { assertEquals, assertThrows } from "jsr:@std/assert";
Deno.test("basic test", () => {
assertEquals(1 + 1, 2);
});
Deno.test("async test", async () => {
const response = await fetch("https://api.example.com/data");
assertEquals(response.status, 200);
});
Deno.test({
name: "permission test",
permissions: { net: true },
async fn() {
const res = await fetch("https://example.com");
assertEquals(res.ok, true);
},
});
// Test coverage
// deno test --coverage=cov_profile
// deno coverage cov_profile --lcov > coverage.lcov
File System Operations
// Read file
const text = await Deno.readTextFile("./data.txt");
const bytes = await Deno.readFile("./image.png");
// Write file
await Deno.writeTextFile("./output.txt", "Hello, World!");
// Watch for changes
const watcher = Deno.watchFs("./src");
for await (const event of watcher) {
console.log(event.kind, event.paths);
}
// Temp files
const tmpFile = await Deno.makeTempFile({ prefix: "deno_" });
Common Workflows
Starting a New Project
# Initialize project with deno.json
deno init my-project
cd my-project
# Or manually create deno.json
cat > deno.json << 'EOF'
{
"tasks": {
"dev": "deno run --watch --allow-net src/main.ts",
"test": "deno test --allow-read"
}
}
EOF
# Run development server
deno task dev
Migrating from Node.js
# Install npm packages without a package.json
# Just import directly:
# import lodash from "npm:lodash";
# Or add to deno.json imports section
# Run node-compatible scripts
deno run --allow-all --node-modules-dir npm_script.js
# Compatibility layer
deno run --allow-all --compat legacy_node.js
Setting Up CI (GitHub Actions)
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- run: deno fmt --check
- run: deno lint
- run: deno test --allow-read --coverage=cov_profile
- run: deno coverage cov_profile --lcov > coverage.lcov
Deploy to Deno Deploy
# Install deployctl
deno install -gArf jsr:@deno/deployctl
# Deploy from local files
deployctl deploy --project=my-project src/main.ts
# Deploy with env vars
deployctl deploy --project=my-project --env=DATABASE_URL=postgres://... src/main.ts
Tips and Best Practices
Use explicit permissions — Avoid --allow-all in production. Grant only the minimum required permissions and use path-specific grants like --allow-read=./data rather than blanket --allow-read.
Prefer JSR over deno.land/x — The JavaScript Registry (jsr.io) is the modern package registry for Deno and Node. It offers type safety, provenance, and better tooling than the old third-party module registry.
Lock your dependencies — Run deno cache --lock=deno.lock src/main.ts to generate a lockfile. Commit deno.lock to version control. Use --frozen in CI to ensure dependencies don’t drift.
Use deno.json tasks — Define all common commands in the tasks field of deno.json instead of a Makefile or shell scripts. This keeps commands portable and self-documenting.
Type-check separately from running — Use deno check src/main.ts in CI for fast type-checking without executing the program.
Leverage built-in tooling — Deno ships with a formatter (deno fmt), linter (deno lint), test runner (deno test), and doc generator (deno doc) — use them instead of separate npm packages.
Watch mode for development — deno run --watch restarts on file changes. deno test --watch re-runs tests. Both are built-in alternatives to nodemon or chokidar.
Vendor dependencies for offline builds — Run deno vendor to download all dependencies into a local vendor/ directory. Useful for air-gapped environments or reproducible builds.
# Vendor all dependencies
deno vendor src/main.ts
# Run using vendored deps (no network needed)
deno run --no-remote --import-map=vendor/import_map.json src/main.ts
Use Deno KV for simple persistence — For apps that need key-value storage without a full database, Deno KV works locally and scales seamlessly on Deno Deploy with zero configuration.