A modern, secure runtime for JavaScript and TypeScript built on V8, Rust, and Tokio.
- Secure by default: No file, network, or environment access without explicit permission
- TypeScript native: First-class TypeScript support without configuration
- Modern standards: Built around web platform APIs (fetch, WebSocket, etc.)
- Single executable: No package manager required, dependencies loaded via URLs
- Tooling included: Built-in formatter, linter, test runner, bundler, and LSP
- No
node_modules, nopackage.jsonby default - Permissions are explicit and granular
- ES modules only (no CommonJS)
- Top-level await supported everywhere
- Uses URLs for imports instead of bare specifiers
# macOS/Linux
curl -fsSL https://deno.land/install.sh | sh
# Windows (PowerShell)
irm https://deno.land/install.ps1 | iex
# Homebrew
brew install deno
# Cargo
cargo install deno --lockeddeno upgrade # Upgrade to latest
deno upgrade --version 1.40.0 # Specific version
deno --version # Check current versionDeno is secure by default. All permissions are denied unless explicitly granted.
| Flag | Description | Example |
|---|---|---|
--allow-read |
File system read access | --allow-read=/tmp |
--allow-write |
File system write access | --allow-write=./data |
--allow-net |
Network access | --allow-net=api.github.com |
--allow-env |
Environment variable access | --allow-env=HOME,PATH |
--allow-run |
Subprocess execution | --allow-run=git,npm |
--allow-ffi |
Foreign Function Interface | --allow-ffi |
--allow-hrtime |
High-resolution time measurement | --allow-hrtime |
--allow-all or -A |
Grant all permissions | -A (use cautiously) |
# Grant specific access
deno run --allow-read=/home/user --allow-net=deno.land script.ts
# Prompt for permissions interactively
deno run --prompt script.ts
# Multiple permissions
deno run --allow-read --allow-write --allow-net script.tsPermissions can be global (--allow-net) or scoped to specific resources (--allow-net=api.com). This prevents malicious code from accessing sensitive resources.
Deno uses ES modules exclusively with URL-based imports.
// Remote imports (from URL)
import { serve } from "https://deno.land/std@0.200.0/http/server.ts";
// Local imports (relative path)
import { helper } from "./utils.ts";
import { config } from "../config.ts";
// Import maps (for cleaner imports)
import { oak } from "oak"; // Requires deno.json configurationDefine in deno.json to use bare specifiers:
{
"imports": {
"oak": "https://deno.land/x/oak@v12.6.0/mod.ts",
"std/": "https://deno.land/std@0.200.0/",
"@/": "./src/"
}
}// Now you can use clean imports
import { Application } from "oak";
import { assertEquals } from "std/assert/mod.ts";
import { utils } from "@/utils.ts";Import maps translate bare specifiers to full URLs, making code cleaner and dependencies centralized.
# Cache dependencies
deno cache deps.ts
# View dependency tree
deno info main.ts
# Reload and bypass cache
deno run --reload main.ts
# Lock dependencies
deno cache --lock=deno.lock --lock-write deps.tsDeno caches remote modules locally. The lock file ensures reproducible builds by pinning exact versions.
# Run TypeScript/JavaScript
deno run script.ts
deno run script.js
# Run from URL
deno run https://deno.land/std/examples/welcome.ts
# Watch mode (auto-restart on changes)
deno run --watch server.ts
# With permissions
deno run --allow-net --allow-read server.ts// test.ts
import { assertEquals } from "https://deno.land/std@0.200.0/assert/mod.ts";
Deno.test("addition works", () => {
assertEquals(1 + 1, 2);
});
Deno.test({
name: "async test",
async fn() {
const result = await Promise.resolve(42);
assertEquals(result, 42);
},
});bash
deno test # Run all tests
deno test file_test.ts # Run specific test
deno test --coverage # Generate coverageBuilt-in test runner with no external dependencies. Tests use Deno.test() API.
Deno.bench("string concat", () => {
let str = "";
for (let i = 0; i < 100; i++) {
str += "a";
}
});deno benchbash
deno doc mod.ts # Generate docs
deno doc --json mod.ts # JSON outputThe Deno standard library provides audited, high-quality modules.
// HTTP server
import { serve } from "https://deno.land/std@0.200.0/http/server.ts";
// File system
import { copy, exists } from "https://deno.land/std@0.200.0/fs/mod.ts";
// Path manipulation
import { join, dirname } from "https://deno.land/std@0.200.0/path/mod.ts";
// Assertions (testing)
import { assertEquals, assertExists } from "https://deno.land/std@0.200.0/assert/mod.ts";
// Encoding/Decoding
import { encode, decode } from "https://deno.land/std@0.200.0/encoding/base64.ts";
// Datetime
import { format, parse } from "https://deno.land/std@0.200.0/datetime/mod.ts";Standard library is versioned separately from runtime. Always pin versions (@0.200.0) for stability.
// Read text file
const text = await Deno.readTextFile("./file.txt");
// Read binary file
const data = await Deno.readFile("./image.png");
// Read directory
for await (const entry of Deno.readDir("./dir")) {
console.log(entry.name, entry.isDirectory);
}// Write text
await Deno.writeTextFile("./output.txt", "Hello Deno");
// Write binary
await Deno.writeFile("./data.bin", new Uint8Array([1, 2, 3]));
// Append to file
await Deno.writeTextFile("./log.txt", "New entry\n", { append: true });// Copy file
await Deno.copyFile("source.txt", "dest.txt");
// Remove file/directory
await Deno.remove("./file.txt");
await Deno.remove("./dir", { recursive: true });
// Rename/Move
await Deno.rename("old.txt", "new.txt");
// File info
const info = await Deno.stat("./file.txt");
console.log(info.size, info.isFile, info.mtime);All file operations are async and require --allow-read and/or --allow-write permissions.
// Simple server
Deno.serve((_req) => new Response("Hello World"));
// With options
Deno.serve({ port: 8080 }, (req) => {
return new Response(`You requested: ${req.url}`);
});
// Using std/http
import { serve } from "https://deno.land/std@0.200.0/http/server.ts";
serve((req) => new Response("Hello"), { port: 3000 });// GET request
const response = await fetch("https://api.github.com/users/denoland");
const data = await response.json();
// POST request
const response = await fetch("https://api.example.com/data", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Deno" }),
});Deno implements standard Web APIs like fetch, making browser code portable to server-side.
// Server
Deno.serve((req) => {
if (req.headers.get("upgrade") === "websocket") {
const { socket, response } = Deno.upgradeWebSocket(req);
socket.onmessage = (e) => socket.send(`Echo: ${e.data}`);
return response;
}
return new Response("Not a WebSocket request");
});
// Client
const ws = new WebSocket("ws://localhost:8080");
ws.onmessage = (e) => console.log(e.data);
ws.send("Hello");{
"compilerOptions": {
"strict": true,
"lib": ["deno.window", "deno.unstable"],
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"lint": {
"rules": {
"tags": ["recommended"],
"exclude": ["no-unused-vars"]
}
},
"fmt": {
"useTabs": false,
"lineWidth": 100,
"semiColons": true
},
"tasks": {
"dev": "deno run --watch main.ts"
},
"imports": {
"react": "https://esm.sh/react@18"
}
}deno.json is the configuration file for TypeScript options, linting, formatting, import maps, and tasks.
# Type check without running
deno check main.ts
# Skip type checking (faster execution)
deno run --no-check main.tsDeno implements many browser-standard Web APIs:
| API | Description |
|---|---|
fetch |
HTTP requests |
WebSocket |
WebSocket connections |
localStorage |
Not available (security) |
setTimeout/setInterval |
Timers |
console |
Logging |
TextEncoder/TextDecoder |
Text encoding |
URL/URLSearchParams |
URL manipulation |
FormData |
Form data handling |
ReadableStream/WritableStream |
Streaming APIs |
crypto |
Cryptographic operations |
Blob/File |
Binary data |
Using standard Web APIs makes code portable between browser and server environments.
Deno can import npm packages directly:
// Using npm: specifier
import express from "npm:express@4";
import { dirname } from "npm:path";
// Using CDN (esm.sh)
import React from "https://esm.sh/react@18";npm: specifier loads packages from npm registry. CDNs like esm.sh provide ESM-compatible versions.
// Import Node.js built-ins
import { readFile } from "node:fs/promises";
import { createServer } from "node:http";node: prefix imports Node.js built-in modules with compatibility layer.
// server.ts
Deno.serve({ port: 8000 }, (req: Request) => {
const url = new URL(req.url);
if (url.pathname === "/") {
return new Response("Home Page");
}
if (url.pathname === "/api") {
return Response.json({ message: "API response" });
}
return new Response("Not Found", { status: 404 });
});
// Run: deno run --allow-net server.tsDeno.serve(async (req) => {
if (req.method === "POST") {
const formData = await req.formData();
const file = formData.get("file") as File;
if (file) {
const bytes = await file.arrayBuffer();
await Deno.writeFile(`./uploads/${file.name}`, new Uint8Array(bytes));
return new Response("File uploaded");
}
}
return new Response("Send POST with file");
});
// Run: deno run --allow-net --allow-write server.ts// config.json
// { "apiKey": "secret", "port": 3000 }
const configText = await Deno.readTextFile("./config.json");
const config = JSON.parse(configText);
console.log(config.apiKey);
// Run: deno run --allow-read script.tsconst isProd = Deno.env.get("ENVIRONMENT") === "production";
const port = parseInt(Deno.env.get("PORT") || "8000");
Deno.serve({ port }, () => {
return new Response(isProd ? "Production" : "Development");
});
// Run: ENVIRONMENT=production PORT=3000 deno run --allow-net --allow-env script.ts// math.ts
export function add(a: number, b: number): number {
return a + b;
}
// math_test.ts
import { assertEquals } from "https://deno.land/std@0.200.0/assert/mod.ts";
import { add } from "./math.ts";
Deno.test("add function", () => {
assertEquals(add(2, 3), 5);
assertEquals(add(-1, 1), 0);
});
// Run: deno test- Pin versions: Always specify exact versions in imports (
@0.200.0) - Use import maps: Centralize dependencies in
deno.jsonfor maintainability - Lock dependencies: Use
deno.lockfor reproducible builds - Vendor dependencies: Cache all deps with
deno cachebefore deployment
- Principle of least privilege: Grant only necessary permissions
- Scope permissions: Use
--allow-net=api.cominstead of--allow-net - Review remote code: Check imported URLs before running
- Use lock files: Prevent supply chain attacks with
deno.lock
- Use meaningful names:
server.ts,utils.ts, notindex.ts - Separate concerns: Split HTTP handlers, business logic, and data access
- Export explicitly: Use named exports for clarity
- Type everything: Leverage TypeScript's type system fully
- Minimize permission checks: Grant permissions once per run, not per operation
- Use streaming: Prefer
ReadableStreamfor large data - Cache wisely: Use
--reloadselectively to update specific modules - Avoid dynamic imports: Static imports enable better optimization
- Test file naming: Use
_test.tsor.test.tssuffix - Isolate tests: Each test should be independent
- Use assertions: Import from
std/assertfor consistent testing - Mock carefully: Use dependency injection for easier mocking
// ❌ Forgot permission flag
await Deno.readFile("./data.txt");
// Run without --allow-read → PermissionDenied error
// ✅ Include required permission
// deno run --allow-read script.ts// ❌ Node-style imports don't work
import { helper } from "./utils"; // Missing .ts extension
// ✅ Always include file extension
import { helper } from "./utils.ts";// ❌ Forgot await
const text = Deno.readTextFile("file.txt");
console.log(text); // Prints Promise object
// ✅ Await async operations
const text = await Deno.readTextFile("file.txt");
console.log(text);// ❌ Different std versions
import { serve } from "https://deno.land/std@0.200.0/http/server.ts";
import { assertEquals } from "https://deno.land/std@0.180.0/assert/mod.ts";
// ✅ Consistent versions
import { serve } from "https://deno.land/std@0.200.0/http/server.ts";
import { assertEquals } from "https://deno.land/std@0.200.0/assert/mod.ts";// ❌ Synchronous file reading (deprecated)
const text = Deno.readTextFileSync("file.txt");
// ✅ Use async version
const text = await Deno.readTextFile("file.txt");# ❌ Granting all permissions unnecessarily
deno run --allow-all script.ts
# ✅ Grant only what's needed
deno run --allow-read=./data --allow-net=api.example.com script.ts// ❌ Implicit any types
function process(data) {
return data.value;
}
// ✅ Explicit types
function process(data: { value: number }): number {
return data.value;
}deno run script.ts # Run script
deno run -A script.ts # Run with all permissions
deno run --watch script.ts # Watch mode
deno test # Run tests
deno fmt # Format code
deno lint # Lint code
deno cache deps.ts # Cache dependencies
deno info # Show Deno info
deno upgrade # Update Deno-A, --allow-all # All permissions
--allow-read[=<PATH>] # File system read
--allow-write[=<PATH>] # File system write
--allow-net[=<HOST>] # Network access
--allow-env[=<VAR>] # Environment variables
--allow-run[=<CMD>] # Run subprocesses// Remote
import { x } from "https://deno.land/std@0.200.0/mod.ts";
// Local
import { y } from "./local.ts";
// npm
import { z } from "npm:package@1.0.0";
// Node built-ins
import { a } from "node:fs/promises";// File I/O
await Deno.readTextFile(path);
await Deno.writeTextFile(path, data);
// HTTP
Deno.serve((req) => new Response("OK"));
await fetch(url);
// Environment
Deno.env.get("VAR");
Deno.args;
// Process
const cmd = new Deno.Command("ls");
await cmd.output();{
"imports": { "alias": "https://url.com/mod.ts" },
"tasks": { "dev": "deno run --watch main.ts" },
"compilerOptions": { "strict": true }
}