Getting started
Installation
# macOS/Linux (Shell)
curl -fsSL https://deno.land/install.sh | sh
# macOS (Homebrew)
brew install deno
# Windows (PowerShell)
irm https://deno.land/install.ps1 | iex
# Cargo (Rust)
cargo install deno --locked
# Verify installation
deno --version
Hello World
// hello.ts
console.log("Hello from Deno!");
// Run it
deno run hello.ts
Quick Example
// fetch.ts
const response = await fetch("https://api.github.com/users/denoland");
const data = await response.json();
console.log(data.name);
# Requires network permission
deno run --allow-net fetch.ts
First HTTP Server
// server.ts
Deno.serve({ port: 8000 }, (_req) => {
return new Response("Hello, World!");
});
# Run with network permission
deno run --allow-net server.ts
Running Code
Basic Execution
# 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)
deno run --watch script.ts
# Check syntax without running
deno check script.ts
REPL (Interactive Shell)
# Start REPL
deno
# REPL with imports
deno repl
# Evaluate expression
deno eval "console.log(1 + 2)"
Script Arguments
// args.ts
console.log(Deno.args);
deno run args.ts arg1 arg2
# Output: ["arg1", "arg2"]
Environment Variables
// Get env var
const home = Deno.env.get("HOME");
// Set env var
Deno.env.set("MY_VAR", "value");
// Delete env var
Deno.env.delete("MY_VAR");
# Run with env permission
deno run --allow-env script.ts
# Run with specific env access
deno run --allow-env=HOME script.ts
Permissions
Security Model
Deno is secure by default. All permissions must be explicitly granted.
| Permission | Flag | Description |
|---|---|---|
| Network | --allow-net |
Network access |
| Read | --allow-read |
File system read |
| Write | --allow-write |
File system write |
| Environment | --allow-env |
Environment variables |
| Run | --allow-run |
Subprocess execution |
| FFI | --allow-ffi |
Foreign Function Interface |
| HRTime | --allow-hrtime |
High-resolution time |
| All | --allow-all or -A |
Grant all permissions |
Permission Examples
# Network access to specific domain
deno run --allow-net=api.github.com fetch.ts
# Read from specific directory
deno run --allow-read=/tmp script.ts
# Multiple permissions
deno run --allow-net --allow-read --allow-write server.ts
# All permissions (unsafe!)
deno run -A script.ts
# Prompt for permissions
deno run --prompt script.ts
Runtime Permission Checks
// Request permission
const status = await Deno.permissions.request({ name: "read" });
if (status.state === "granted") {
const data = await Deno.readTextFile("file.txt");
}
// Query permission status
const netStatus = await Deno.permissions.query({ name: "net" });
console.log(netStatus.state); // "granted" | "denied" | "prompt"
// Revoke permission
await Deno.permissions.revoke({ name: "read" });
Modules & Imports
ESM Imports
// URL imports (no package.json)
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
// Relative imports
import { helper } from "./utils.ts";
import { config } from "../config.ts";
// Standard library
import { assertEquals } from "https://deno.land/std@0.208.0/assert/mod.ts";
Import Maps
// import_map.json
{
"imports": {
"std/": "https://deno.land/std@0.208.0/",
"oak": "https://deno.land/x/oak@v12.6.1/mod.ts",
"~/": "./src/"
}
}
// Use with import map
import { serve } from "std/http/server.ts";
import { Application } from "oak";
import { helper } from "~/utils.ts";
# Run with import map
deno run --import-map=import_map.json script.ts
deno.json Configuration
{
"imports": {
"std/": "https://deno.land/std@0.208.0/",
"~/": "./src/"
},
"tasks": {
"dev": "deno run --watch main.ts"
},
"lint": {
"include": ["src/"]
},
"fmt": {
"useTabs": false,
"indentWidth": 2
}
}
npm Compatibility
// Import npm packages with npm: specifier
import express from "npm:express@4";
import chalk from "npm:chalk@5";
// Node built-ins with node: specifier
import { readFile } from "node:fs/promises";
import { Buffer } from "node:buffer";
Standard Library
Common Modules
// HTTP server
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
// File system utilities
import { ensureDir, copy } from "https://deno.land/std@0.208.0/fs/mod.ts";
// Path manipulation
import { join, dirname } from "https://deno.land/std@0.208.0/path/mod.ts";
// Assertions (testing)
import {
assertEquals,
assertExists,
} from "https://deno.land/std@0.208.0/assert/mod.ts";
// Encoding/Decoding
import {
encode,
decode,
} from "https://deno.land/std@0.208.0/encoding/base64.ts";
File System
// Read file
const text = await Deno.readTextFile("file.txt");
const bytes = await Deno.readFile("file.bin");
// Write file
await Deno.writeTextFile("output.txt", "content");
await Deno.writeFile("output.bin", new Uint8Array([1, 2, 3]));
// Directory operations
for await (const entry of Deno.readDir(".")) {
console.log(entry.name);
}
// File info
const info = await Deno.stat("file.txt");
console.log(info.size, info.isFile, info.mtime);
HTTP Client
// Fetch API (built-in)
const response = await fetch("https://api.example.com/data");
const json = await response.json();
// With headers
const response2 = await fetch("https://api.example.com/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: "value" }),
});
HTTP Server
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
// Simple server
serve((_req) => new Response("Hello!"), { port: 8000 });
// With routing
serve((req) => {
const url = new URL(req.url);
if (url.pathname === "/") {
return new Response("Home");
}
return new Response("Not Found", { status: 404 });
});
// Modern Deno.serve (v1.35+)
Deno.serve({ port: 8000 }, (req) => {
return new Response("Hello from Deno.serve!");
});
Testing
Basic Tests
// math_test.ts
import { assertEquals } from "https://deno.land/std@0.208.0/assert/mod.ts";
Deno.test("addition works", () => {
assertEquals(1 + 1, 2);
});
Deno.test("async test", async () => {
const response = await Promise.resolve(42);
assertEquals(response, 42);
});
# Run tests
deno test
# Run specific test file
deno test math_test.ts
# Watch mode
deno test --watch
# Filter tests
deno test --filter "addition"
Assertions
import {
assertEquals,
assertNotEquals,
assertExists,
assertStringIncludes,
assertMatch,
assertThrows,
assertRejects,
} from "https://deno.land/std@0.208.0/assert/mod.ts";
// Value assertions
assertEquals(actual, expected);
assertNotEquals(actual, expected);
assertExists(value);
// String assertions
assertStringIncludes("hello world", "world");
assertMatch("deno", /de[no]+/);
// Error assertions
assertThrows(() => {
throw new Error();
});
await assertRejects(() => Promise.reject(new Error()));
Test Steps
Deno.test("multi-step test", async (t) => {
await t.step("setup", () => {
// Setup code
});
await t.step("execute", () => {
// Test code
});
await t.step("teardown", () => {
// Cleanup code
});
});
Mocking & Spying
import {
spy,
assertSpyCalls,
} from "https://deno.land/std@0.208.0/testing/mock.ts";
Deno.test("spy example", () => {
const func = spy();
func("arg1", "arg2");
assertSpyCalls(func, 1);
});
Formatting & Linting
deno fmt (Formatter)
# Format files
deno fmt
# Format specific files
deno fmt src/
# Check without writing
deno fmt --check
# Ignore files
deno fmt --ignore=dist/
# Format with options
deno fmt --use-tabs --line-width=100
Configuration (deno.json)
{
"fmt": {
"useTabs": false,
"lineWidth": 80,
"indentWidth": 2,
"semiColons": true,
"singleQuote": false,
"proseWrap": "preserve",
"include": ["src/"],
"exclude": ["dist/", "coverage/"]
}
}
deno lint (Linter)
# Lint files
deno lint
# Lint specific files
deno lint src/
# Lint with JSON output
deno lint --json
# List available rules
deno lint --rules
# Ignore specific rules
deno lint --ignore=no-unused-vars
Lint Configuration
{
"lint": {
"include": ["src/"],
"exclude": ["dist/"],
"rules": {
"tags": ["recommended"],
"include": ["ban-untagged-todo"],
"exclude": ["no-unused-vars"]
}
}
}
Inline Lint Directives
// Ignore next line
// deno-lint-ignore no-explicit-any
const data: any = {};
// Ignore entire file
// deno-lint-ignore-file no-unused-vars
Task Runner
deno.json Tasks
{
"tasks": {
"dev": "deno run --watch --allow-net main.ts",
"start": "deno run --allow-net --allow-read main.ts",
"test": "deno test --allow-read",
"bench": "deno bench",
"bundle": "deno bundle main.ts dist/bundle.js"
}
}
# Run tasks
deno task dev
deno task test
deno task start
Task Composition
{
"tasks": {
"lint": "deno lint",
"fmt": "deno fmt",
"test": "deno test",
"check": "deno task lint && deno task fmt --check && deno task test"
}
}
Shell Commands
{
"tasks": {
"clean": "rm -rf dist/",
"build": "deno task clean && deno compile main.ts"
}
}
Node.js Compatibility
npm Packages
// Direct npm imports
import express from "npm:express@4.18.2";
import lodash from "npm:lodash@4.17.21";
// With types
import React from "npm:react@18";
import type { ReactNode } from "npm:@types/react@18";
// CommonJS modules
const _ = require("npm:lodash");
Node Built-ins
// Node.js built-in modules with node: specifier
import { readFile } from "node:fs/promises";
import { createServer } from "node:http";
import { Buffer } from "node:buffer";
import { EventEmitter } from "node:events";
import path from "node:path";
package.json Support
// package.json
{
"dependencies": {
"express": "^4.18.2",
"lodash": "^4.17.21"
}
}
// Import from package.json dependencies
import express from "express";
# Run with package.json
deno run --allow-read --allow-net main.ts
Compatibility Mode
# Run Node.js scripts
deno run --compat --unstable main.js
# Use npm install (creates node_modules)
deno run --allow-read --allow-write npm:npm install
Deployment
Compile to Binary
# Compile to executable
deno compile --output=myapp main.ts
# Cross-compile
deno compile --target=x86_64-unknown-linux-gnu main.ts
deno compile --target=x86_64-pc-windows-msvc main.ts
deno compile --target=x86_64-apple-darwin main.ts
deno compile --target=aarch64-apple-darwin main.ts
# With permissions
deno compile --allow-net --allow-read main.ts
Deno Deploy
// main.ts for Deno Deploy
Deno.serve((_req) => {
return new Response("Hello from Deno Deploy!");
});
# Deploy with deployctl
deno install --allow-all --no-check -r -f https://deno.land/x/deploy/deployctl.ts
deployctl deploy --project=my-project main.ts
GitHub Actions
# .github/workflows/deno.yml
name: Deno CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: denoland/setup-deno@v1
with:
deno-version: v1.x
- run: deno lint
- run: deno fmt --check
- run: deno test --allow-read
Docker
FROM denoland/deno:1.39.0
WORKDIR /app
COPY . .
RUN deno cache main.ts
EXPOSE 8000
CMD ["run", "--allow-net", "main.ts"]
Advanced Features
Workers
// main.ts
const worker = new Worker(new URL("./worker.ts", import.meta.url).href, {
type: "module",
deno: {
permissions: {
net: true,
},
},
});
worker.postMessage({ cmd: "fetch" });
worker.onmessage = (e) => console.log(e.data);
// worker.ts
self.onmessage = async (e) => {
if (e.data.cmd === "fetch") {
const response = await fetch("https://api.example.com");
self.postMessage(await response.json());
}
};
WebAssembly
// Load and run WASM
const wasmCode = await Deno.readFile("./module.wasm");
const wasmModule = new WebAssembly.Module(wasmCode);
const wasmInstance = new WebAssembly.Instance(wasmModule);
const result = wasmInstance.exports.add(5, 3);
console.log(result);
FFI (Foreign Function Interface)
// Call native libraries
const dylib = Deno.dlopen("./libexample.so", {
add: { parameters: ["i32", "i32"], result: "i32" },
});
const result = dylib.symbols.add(5, 3);
console.log(result);
dylib.close();
# Run with FFI permission
deno run --allow-ffi --unstable script.ts
WebSocket
// WebSocket client
const ws = new WebSocket("wss://echo.websocket.org");
ws.onopen = () => {
ws.send("Hello Server!");
};
ws.onmessage = (e) => {
console.log("Message:", e.data);
};
ws.onclose = () => console.log("Disconnected");
Common Tasks
Dependency Management
# Cache dependencies
deno cache deps.ts
# Reload dependencies
deno cache --reload deps.ts
# Show dependency tree
deno info main.ts
# Update dependencies
deno cache --reload=https://deno.land/std main.ts
Vendoring Dependencies
# Vendor remote dependencies
deno vendor main.ts
# Creates vendor/ directory with all deps
deno run --import-map=vendor/import_map.json main.ts
Lock File
# Generate lock file
deno cache --lock=deno.lock --lock-write deps.ts
# Check integrity
deno cache --lock=deno.lock deps.ts
Documentation
# Generate docs
deno doc main.ts
# Export as JSON
deno doc --json main.ts > docs.json
# Serve docs
deno doc --html --name="My Project" main.ts
Benchmarking
// bench.ts
Deno.bench("string concat", () => {
let str = "";
for (let i = 0; i < 100; i++) {
str += "a";
}
});
Deno.bench("array join", () => {
const arr = [];
for (let i = 0; i < 100; i++) {
arr.push("a");
}
arr.join("");
});
# Run benchmarks
deno bench
Migration from Node.js
Key Differences
| Feature | Node.js | Deno |
|---|---|---|
| Package manager | npm/yarn/pnpm | Built-in (URL imports) |
| Module system | CommonJS/ESM | ESM only |
| TypeScript | Requires setup | Native support |
| Security | No restrictions | Secure by default |
| Tooling | External tools | Built-in fmt/lint/test |
| Configuration | package.json | deno.json |
Import Changes
// Node.js
const express = require("express");
import express from "express";
// Deno
import express from "npm:express@4";
// Node.js built-ins
// Node: import { readFile } from "fs/promises";
// Deno: import { readFile } from "node:fs/promises";
Common Replacements
// __dirname and __filename
// Node.js: __dirname, __filename
// Deno:
const __filename = new URL("", import.meta.url).pathname;
const __dirname = new URL(".", import.meta.url).pathname;
// process.env
// Node.js: process.env.VAR
// Deno: Deno.env.get("VAR")
// process.exit()
// Node.js: process.exit(1)
// Deno: Deno.exit(1)
// Buffer
// Node.js: Buffer.from("hello")
// Deno: new TextEncoder().encode("hello")
Express Server Example
// Node.js
const express = require("express");
const app = express();
app.get("/", (req, res) => res.send("Hello!"));
app.listen(3000);
// Deno
import express from "npm:express@4";
const app = express();
app.get("/", (_req, res) => res.send("Hello!"));
app.listen(3000);
Gotchas & Tips
Common Mistakes
Forgetting Permissions
# ❌ Error: Requires network access
deno run fetch.ts
# ✅ Grant network permission
deno run --allow-net fetch.ts
URL Import Versioning
// ❌ No version pinning
import { serve } from "https://deno.land/std/http/server.ts";
// ✅ Pin versions
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
Missing File Extensions
// ❌ No extension
import { helper } from "./utils";
// ✅ Include extension
import { helper } from "./utils.ts";
Performance Tips
// Use Deno.serve (faster than std/http)
Deno.serve({ port: 8000 }, handler);
// Cache dependencies
// deno cache --lock=deno.lock deps.ts
// Compile for production
// deno compile --allow-net main.ts
Best Practices
- Pin versions in imports:
std@0.208.0 - Use deno.json for configuration
- Enable --watch during development
- Leverage built-in tools (fmt, lint, test)
- Use import maps for cleaner imports
- Request minimum permissions needed
- Cache deps in CI/CD pipelines
Also see
- Deno Official Docs - Comprehensive manual
- Deno Standard Library - Official standard library
- Deno Deploy - Serverless platform for Deno
- Deno by Example - Learn by example
- Third Party Modules - Community modules
- Deno on GitHub - Source code and issues
- Awesome Deno - Curated list of resources