Deno 2 Reference: TypeScript, HTTP Server, KV Store, npm Compatibility & Deployment
Deno 2 is TypeScript-first, secure by default, and now ships with Node.js/npm compatibility. The three big differences from Node.js: no node_modules by default (uses a central cache), explicit permission flags for file/network/env access, and first-class TypeScript without a build step. Deno 2 added deno.json workspaces, deno install, and near-complete npm package support.
1. Setup, Running & Permission Model
Install, run TypeScript directly, and understand the permission system
# Install:
curl -fsSL https://deno.land/install.sh | sh
# Run TypeScript directly (no compile step):
deno run main.ts
# Permission flags (deny-all by default — must be explicit):
deno run --allow-net main.ts # all network
deno run --allow-net=api.example.com main.ts # specific host only
deno run --allow-read=/tmp main.ts # specific path only
deno run --allow-env=DATABASE_URL main.ts # specific env var only
deno run --allow-all main.ts # all permissions (dev only)
# Common flags combination:
deno run \
--allow-net=api.example.com,db.example.com \
--allow-read=./data \
--allow-env=DATABASE_URL,PORT \
main.ts
# deno.json (replaces package.json + tsconfig.json):
{
"tasks": {
"dev": "deno run --watch --allow-net --allow-read main.ts",
"test": "deno test --allow-net",
"fmt": "deno fmt",
"lint": "deno lint"
},
"imports": {
"@std/http": "jsr:@std/http@^1.0.0",
"zod": "npm:zod@^3.22.0"
},
"compilerOptions": {
"strict": true
}
}
# Run task:
deno task dev
2. HTTP Server & Hono Framework
Built-in Deno.serve, Hono framework, and request/response handling
// Built-in HTTP server (Deno.serve — no npm package needed):
Deno.serve({ port: 8000 }, async (req: Request) => {
const url = new URL(req.url);
if (url.pathname === "/health") {
return new Response("OK", { status: 200 });
}
if (req.method === "POST" && url.pathname === "/api/users") {
const body = await req.json();
return Response.json({ id: crypto.randomUUID(), ...body }, { status: 201 });
}
return new Response("Not Found", { status: 404 });
});
// Hono (recommended for real apps — runs on Deno, Node, Cloudflare Workers, Bun):
// deno.json: "hono": "npm:hono@^4.0.0"
import { Hono } from "hono";
import { logger } from "hono/logger";
import { jwt } from "hono/jwt";
const app = new Hono();
app.use("*", logger());
// Typed route params:
app.get("/users/:id", async (c) => {
const id = c.req.param("id");
const user = await db.getUser(id);
if (!user) return c.json({ error: "Not found" }, 404);
return c.json(user);
});
// JWT middleware on a group:
const api = app.basePath("/api");
api.use("*", jwt({ secret: Deno.env.get("JWT_SECRET")! }));
api.get("/me", (c) => {
const payload = c.get("jwtPayload");
return c.json({ userId: payload.sub });
});
Deno.serve({ port: 8000 }, app.fetch);
3. File System, KV Store & WebSocket
Deno.readFile/writeFile, Deno.KV (built-in key-value store), and WebSocket server
// File I/O (standard Web APIs + Deno namespace):
const text = await Deno.readTextFile("./data.json");
const data = JSON.parse(text);
await Deno.writeTextFile("./output.json", JSON.stringify(data, null, 2));
// Stream large files:
const file = await Deno.open("./large.csv");
for await (const line of file.readable.pipeThrough(new TextDecoderStream()).pipeThrough(new TransformStream())) {
// process each chunk
}
// Deno KV (built-in SQLite-backed key-value — no Redis needed for simple cases):
const kv = await Deno.openKv(); // --unstable-kv flag in Deno 1.x, stable in Deno 2
// Set with optional expiration:
await kv.set(["users", userId], { name: "Alice", email: "alice@example.com" });
await kv.set(["sessions", sessionId], { userId }, { expireIn: 86400000 }); // 24h TTL
// Get (returns { key, value, versionstamp } or { value: null }):
const result = await kv.get(["users", userId]);
if (result.value) console.log(result.value.name);
// List by prefix:
const iter = kv.list({ prefix: ["users"] });
for await (const entry of iter) {
console.log(entry.key, entry.value);
}
// Atomic transaction (compare-and-swap):
const res = await kv.atomic()
.check({ key: ["counter"], versionstamp: current.versionstamp })
.set(["counter"], current.value + 1)
.commit();
if (!res.ok) console.log("conflict, retry");
// WebSocket server:
Deno.serve({ port: 8080 }, (req) => {
if (req.headers.get("upgrade") !== "websocket") {
return new Response("Not a WebSocket request", { status: 400 });
}
const { socket, response } = Deno.upgradeWebSocket(req);
socket.onopen = () => socket.send("connected");
socket.onmessage = (e) => socket.send(`echo: ${e.data}`);
return response;
});
4. npm Compatibility & Module System
Import npm packages, use JSR registry, and manage dependencies
// Import npm packages directly (Deno 2):
import express from "npm:express@^4"; // npm: specifier
import { z } from "npm:zod@^3.22";
import { PrismaClient } from "npm:@prisma/client";
// JSR (JavaScript Registry — Deno-native, type-safe):
import { assertEquals } from "jsr:@std/assert@^1";
import { parse } from "jsr:@std/csv@^1";
// deno.json imports map (cleaner than inline specifiers):
// "imports": { "zod": "npm:zod@^3.22", "@std/assert": "jsr:@std/assert@^1" }
import { z } from "zod";
// Lock file (generated automatically):
deno cache --lock=deno.lock main.ts // generate
deno run --lock=deno.lock main.ts // enforce in CI
// Node.js compatibility (Deno 2 — most npm packages work):
import { readFileSync } from "node:fs"; // node: prefix
import path from "node:path";
import { createServer } from "node:http";
// What still doesn't work: packages that use native addons (.node files),
// some packages that read process.cwd() in ways Deno doesn't support,
// packages that need Node.js-specific globals at module level
5. Testing, Benchmarking & Deployment
Built-in test runner, bench(), Docker, and Deno Deploy
// Built-in test runner (no Jest/Vitest needed):
import { assertEquals, assertRejects } from "jsr:@std/assert@^1";
Deno.test("adds two numbers", () => {
assertEquals(1 + 2, 3);
});
Deno.test("async test", async () => {
const res = await fetch("http://localhost:8000/health");
assertEquals(res.status, 200);
});
Deno.test({
name: "skipped test",
ignore: true,
fn: () => {},
});
// Sub-tests (grouping):
Deno.test("UserService", async (t) => {
await t.step("creates user", async () => {
const user = await UserService.create({ name: "Alice" });
assertEquals(user.name, "Alice");
});
await t.step("throws on duplicate email", async () => {
await assertRejects(() => UserService.create({ email: "dupe@test.com" }));
});
});
// deno test --watch --allow-net
// Benchmarking:
Deno.bench("JSON parse", () => {
JSON.parse('{"name":"Alice","age":30}');
});
// Docker (single binary, tiny image):
FROM denoland/deno:2.1.0
WORKDIR /app
COPY deno.json deno.lock ./
RUN deno cache main.ts
COPY . .
EXPOSE 8000
CMD ["deno", "run", "--allow-net", "--allow-env", "--allow-read", "main.ts"]
# Compile to single binary:
deno compile --allow-net --allow-env --output=server main.ts
# Deno Deploy (zero-config edge deployment):
deployctl deploy --project=my-app main.ts
Track Deno releases at ReleaseRun. Related: Node.js Reference | TypeScript Reference | WebSocket Reference
🔍 Free tool: npm Package Health Checker — for npm-compatible packages in Deno, check any package for EOL status and known CVEs instantly.
Founded
2023 in London, UK
Contact
hello@releaserun.com