Deno (pronounced “deno”) is a modern JavaScript runtime created by Ryan Dahl, the original creator of Node.js. Built in Rust with V8, Deno aims to fix design mistakes in Node.js while providing a secure, modern development experience.
What is Deno?
Deno is a JavaScript runtime that provides:
- Native TypeScript support - No transpilation needed
- Secure by default - Explicit permission system
- ES Modules - URL-based module loading
- Built-in tooling - Linter, formatter, test runner
- URL-based imports - No node_modules
# Install Deno
curl -fsSL https://deno.land/install.sh | sh
# Windows (via PowerShell)
irm https://deno.land/install.ps1 | iex
# Run TypeScript directly
deno run app.ts
# Run JavaScript
deno run app.js
Key Features
1. Native TypeScript Support
// app.ts - Direct execution
interface User {
id: number;
name: string;
email: string;
}
const users: User[] = [
{ id: 1, name: "Alice", email: "[email protected]" },
{ id: 2, name: "Bob", email: "[email protected]" }
];
users.forEach(user => {
console.log(`${user.name}: ${user.email}`);
});
// Export for other modules
export { users };
2. Security by Default
Deno runs in a sandbox with no file system or network access by default:
// This will fail - no permissions
const file = await Deno.readTextFile("secret.txt");
// Error: Permission denied
// Request permissions at runtime
const permission = await Deno.permissions.query({ name: "read", path: "./config.json" });
# Grant permissions explicitly
deno run --allow-read app.ts
deno run --allow-net --allow-env app.ts
# Multiple permissions
deno run -A app.ts # All permissions (not recommended)
deno run --allow-read=. --allow-net=localhost:3000 app.ts
3. URL-Based Imports
// Import from URLs
import { serve } from "https://deno.land/[email protected]/http/server.ts";
import lodash from "https://cdn.skypack.dev/lodash";
// Import from npm (new in Deno 1.25+)
import express from "npm:express@4";
4. Built-in Tools
# Format code
deno fmt
# Lint
deno lint
# Type check
deno check app.ts
# Run tests
deno test
# Bundle
deno bundle app.ts app.js
# Doc generation
deno doc app.ts
Deno vs Node.js
| Feature | Deno | Node.js |
|---|---|---|
| Language | Rust | C++ |
| Engine | V8 | V8 |
| TypeScript | Native | Via transpiler |
| Package Manager | URL imports/npm | npm |
| ES Modules | Default | CommonJS default |
| Permissions | Explicit grants | Full access |
| Top-level await | Yes | Yes (ESM) |
| Worker Threads | Web Workers | worker_threads |
| Type Checking | Built-in | Via TypeScript |
Getting Started
Hello World
// hello.ts
console.log("Hello from Deno!");
// Run it
deno run hello.ts
HTTP Server
// server.ts
const handler = (request: Request): Response => {
return new Response("Hello from Deno!", {
headers: { "Content-Type": "text/plain" }
});
Deno.serve(handler);
// Alternative using std library
import { serve } from "https://deno.land/[email protected]/http/server.ts";
serve(handler);
File Operations
// Read file
const text = await Deno.readTextFile("example.txt");
// Write file
await Deno.writeTextFile("output.txt", "Hello World");
// Check if file exists
const exists = await Deno.stat("example.txt").then(() => true).catch(() => false);
// Read as JSON
const config = JSON.parse(await Deno.readTextFile("config.json"));
Working with JSON
import { serve } from "https://deno.land/[email protected]/http/server.ts";
const api = serve(async (req) => {
const data = {
message: "Hello API",
timestamp: new Date().toISOString(),
version: "1.0.0"
};
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" }
});
});
serve(api);
Deno Modules
Using Standard Library
// Import from Deno's standard library
import {
readAll,
writeAll
} from "https://deno.land/[email protected]/streams/conversion.ts";
import {
parse as parseArgs
} from "https://deno.land/[email protected]/flags/mod.ts";
import {
glob
} from "https://deno.land/[email protected]/fs/glob.ts";
Using npm Packages
// Direct npm imports (Deno 1.25+)
import express from "npm:express@4";
import cowsay from "npm:cowsay@1";
const app = express();
app.get("/", (req, res) => {
res.send(cowsay.say({ text: "Hello from Deno!" }));
});
app.listen(3000);
Using Third-Party Modules
// Oak framework
import { Application, Router } from "https://deno.land/x/[email protected]/mod.ts";
const app = new Application();
const router = new Router();
router.get("/", (ctx) => {
ctx.response.body = "Hello from Oak!";
});
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: 3000 });
Testing in Deno
Writing Tests
import { assertEquals, assertExists } from "https://deno.land/[email protected]/testing/asserts.ts";
Deno.test("simple test", () => {
assertEquals(1 + 1, 2);
});
Deno.test("async test", async () => {
const data = await fetch("https://api.example.com/data");
assertEquals(data.ok, true);
});
Deno.test("test with setup", async (t) => {
const server = await startTestServer();
await t.step("test endpoint", async () => {
const res = await fetch(server.url);
assertEquals(res.status, 200);
});
await server.stop();
});
Running Tests
# Run all tests
deno test
# Run specific file
deno test my_test.ts
# Run with coverage
deno test --coverage
# Watch mode
deno test --watch
Deno Deploy
Deploying to Deno Deploy
// deploy.ts
export default {
fetch(request: Request): Response {
return new Response("Deployed on Deno Deploy!");
}
};
# Deploy via CLI
deno deploy deploy.ts
# Or use GitHub integration
# Connect your GitHub repo to Deno Deploy
KV Database
// Deno KV - built-in key-value store
const kv = await Deno.openKv();
await kv.set(["users", "alice"], { name: "Alice", email: "[email protected]" });
const user = await kv.get(["users", "alice"]);
console.log(user.value);
// Query
const iter = kv.list({ prefix: ["users"] });
for await (const res of iter) {
console.log(res.key, res.value);
}
Deno Fresh Framework
Creating a Fresh Project
# Create new Fresh project
deno run -A -r https://fresh.deno.dev my-project
cd my-project
deno task start
Fresh Route Structure
// routes/index.tsx
import { PageProps } from "$fresh/server.ts";
export default function Home({ url }: PageProps) {
return (
<html>
<head>
<title>Fresh App</title>
</head>
<body>
<h1>Hello Fresh!</h1>
<p>Current path: {url.pathname}</p>
</body>
</html>
);
}
Interactive Islands
// islands/Counter.tsx
import { useState } from "preact/hooks";
export function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
Environment Variables
Using dotenv
// Load .env file
import "https://deno.land/x/dotenv/load.ts";
console.log(Deno.env.get("DATABASE_URL"));
# .env file
DATABASE_URL=postgres://localhost:5432/mydb
API_KEY=secret123
# Or via CLI
deno run --env -A app.ts
Configuration
deno.json
{
"compilerOptions": {
"allowJs": true,
"lib": ["deno.window"],
"strict": true
},
"imports": {
"$std/": "https://deno.land/[email protected]/",
"$fresh/": "https://deno.land/x/[email protected]/"
},
"tasks": {
"dev": "deno run -A --watch=static/,routes/ dev.ts",
"build": "deno run -A dev.ts build",
"start": "deno run -A main.ts"
},
"lint": {
"include": ["routes/"],
"rules": {
"tags": ["fresh", "recommended"]
}
},
"exclude": ["**/_fresh/*"]
}
Common Patterns
Error Handling
try {
const data = await Deno.readTextFile("data.json");
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
console.log("File not found");
} else {
console.error("Error:", error);
}
}
Workers
// main.ts
const worker = new Worker(
new URL("./worker.ts", import.meta.url).href,
{ type: "module" }
);
worker.onmessage = (e) => {
console.log("Result:", e.data);
};
worker.postMessage("Hello worker");
// worker.ts
self.onmessage = (e) => {
self.postMessage(`Processed: ${e.data}`);
};
HTTP Client
const response = await fetch("https://api.example.com/data", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name: "Alice" }),
});
const data = await response.json();
Migration from Node.js
CommonJS to ESM
// Node.js CommonJS
const fs = require("fs");
const path = require("path");
const { foo } = require("./module");
// Deno ESM
import * as fs from "https://deno.land/[email protected]/node/fs.ts";
import { join } from "https://deno.land/[email protected]/node/path.ts";
import { foo } from "./module.ts";
Package.json to Imports
// Node.js package.json
{
"dependencies": {
"express": "^4.18.0",
"lodash": "^4.17.0"
}
}
// Deno - use imports in code
import express from "npm:express@4";
import _ from "npm:lodash@4";
// Or create deno.json imports
{
"imports": {
"express": "npm:express@4",
"lodash": "npm:lodash@4"
}
}
When to Use Deno
Good Use Cases
- TypeScript projects - Native TS support
- Security-sensitive apps - Permission system
- Edge deployment - Deno Deploy, Cloudflare Workers
- Modern tooling - Built-in linter, formatter
- Fresh framework - Island architecture
- Simple projects - No node_modules, fast startup
When to Stick with Node.js
- Enterprise apps - Larger ecosystem
- Legacy projects - CommonJS compatibility
- Windows server - Better Node.js support
- Native modules - Some packages don’t work in Deno
- Team familiarity - Existing Node.js expertise
Comments