Deno Basics
Deno runtime fundamentals including permissions, module system, and built-in tooling
You are an expert in Deno runtime fundamentals for building secure, modern applications with first-class TypeScript support. ## Key Points - **Pin dependency versions** in import URLs or use an import map in `deno.json` so builds are reproducible. - **Use JSR** (`jsr:`) as the primary registry; it offers type-checked publishing and is Deno-native. - **Scope permissions narrowly** — grant `--allow-read=./data` instead of blanket `--allow-read`. - **Use `deno.lock`** (generated automatically) and commit it to version control for deterministic installs. - **Prefer Web Standard APIs** (`fetch`, `Request`, `Response`, `crypto`, `ReadableStream`) — they are built in and portable. - **Run `deno fmt` and `deno lint`** in CI; they require zero configuration. - **Forgetting permissions flags** — your script will throw a `PermissionDenied` error at runtime, not at import time. Test with the same flags you'll use in production. - **Using bare specifiers without an import map** — `import "lodash"` will fail unless you map it in `deno.json` imports or prefix with `npm:`. - **Caching surprises** — Deno caches remote modules globally. Run `deno cache --reload mod.ts` to force re-download after upstream changes. - **Mixing `Deno.*` APIs with Node APIs** — prefer one or the other. Using both leads to confusing permission and compatibility issues. - **Assuming `__dirname` and `__filename` exist** — these are Node-isms. Use `import.meta.dirname` and `import.meta.filename` in Deno 1.40+ or `import.meta.url` with `URL` for older versions.
skilldb get deno-bun-skills/Deno BasicsFull skill: 183 linesDeno Basics — Modern JS Runtimes
You are an expert in Deno runtime fundamentals for building secure, modern applications with first-class TypeScript support.
Overview
Deno is a secure runtime for JavaScript and TypeScript created by Ryan Dahl. It runs TypeScript natively without a compilation step, uses URL-based ES module imports, and enforces an explicit permissions model so that scripts cannot access the filesystem, network, or environment unless granted.
Core Concepts
Permissions Model
Deno is secure by default. All access to the filesystem, network, environment variables, and subprocesses must be explicitly granted via CLI flags.
| Flag | Grants |
|---|---|
--allow-read[=path] | Filesystem read access |
--allow-write[=path] | Filesystem write access |
--allow-net[=host] | Network access |
--allow-env[=VAR] | Environment variable access |
--allow-run[=cmd] | Subprocess execution |
--allow-ffi | Foreign function interface |
-A | All permissions (development only) |
Module System
Deno uses URL-based ES module imports. There is no node_modules directory or package.json required, though both are supported via compatibility features.
// Import from the Deno standard library
import { serve } from "https://deno.land/std@0.224.0/http/server.ts";
// Import from JSR (recommended registry)
import { z } from "jsr:@zod/zod@3";
// Import from npm (compatibility layer)
import chalk from "npm:chalk@5";
deno.json Configuration
{
"tasks": {
"dev": "deno run --watch --allow-net main.ts",
"test": "deno test --allow-read"
},
"imports": {
"@std/http": "jsr:@std/http@1",
"@std/assert": "jsr:@std/assert@1"
},
"compilerOptions": {
"strict": true
}
}
Built-in Tooling
Deno ships with a formatter, linter, test runner, bundler, and documentation generator:
deno fmt # Format source files
deno lint # Lint source files
deno test # Run tests
deno doc mod.ts # Generate documentation
deno bench # Run benchmarks
deno compile # Compile to standalone binary
Implementation Patterns
HTTP Server
Deno.serve({ port: 8000 }, (req: Request): Response => {
const url = new URL(req.url);
if (url.pathname === "/api/health") {
return Response.json({ status: "ok" });
}
return new Response("Not Found", { status: 404 });
});
File Operations
// Reading a file
const content = await Deno.readTextFile("./data.json");
const data = JSON.parse(content);
// Writing a file
await Deno.writeTextFile("./output.json", JSON.stringify(data, null, 2));
// Streaming a large file
const file = await Deno.open("./large.csv", { read: true });
const readable = file.readable;
for await (const chunk of readable) {
// process chunk
}
Testing
import { assertEquals, assertThrows } from "jsr:@std/assert";
Deno.test("addition works", () => {
assertEquals(2 + 2, 4);
});
Deno.test("async operation", async () => {
const resp = await fetch("https://api.example.com/data");
assertEquals(resp.status, 200);
});
Deno.test({
name: "requires file access",
permissions: { read: true },
fn: async () => {
const text = await Deno.readTextFile("./fixture.txt");
assertEquals(text.length > 0, true);
},
});
Environment Variables
// Requires --allow-env
const port = Deno.env.get("PORT") ?? "8000";
const dbUrl = Deno.env.get("DATABASE_URL");
if (!dbUrl) {
console.error("DATABASE_URL is required");
Deno.exit(1);
}
Core Philosophy
Deno was created to address the design mistakes of Node.js, as identified by Node's own creator. The three pillars of Deno's philosophy are security by default, web standards alignment, and first-class TypeScript support. Each of these is a reaction to a specific pain point: Node's unchecked access to the filesystem and network, Node's divergence from browser APIs, and Node's reliance on external tooling for TypeScript.
The permissions model is Deno's most distinctive feature and its most important design decision. By requiring explicit flags for filesystem, network, and environment access, Deno makes the security surface of every script visible at the command line. You can see exactly what a script can do before running it, and you can grant the minimum permissions needed. This is a stark contrast to Node, where any npm package can read your filesystem, make network requests, and access environment variables without restriction.
Deno's module system uses URLs as identifiers, which eliminates the node_modules directory, the package.json configuration file, and the centralized npm registry as mandatory dependencies. Modules are cached globally, referenced by URL, and resolved without a resolution algorithm. This is simpler and more predictable, but it also shifts the burden of version management to the developer, which is why import maps in deno.json and the JSR registry exist to provide familiar ergonomics without the complexity of node_modules.
Anti-Patterns
-
Running everything with
-A(all permissions). Granting blanket permissions in development creates a false sense of what the script actually needs. When you deploy with scoped permissions, missing grants cause runtime crashes. Develop with the same permission flags you will use in production. -
Using bare specifiers without an import map. Writing
import "lodash"fails in Deno unless the import map indeno.jsonmaps it to annpm:or JSR specifier. Always define mappings for bare specifiers to avoid confusing resolution errors. -
Mixing Deno-native and Node APIs for the same concern. Using
Deno.readTextFilein one file andimport { readFile } from "node:fs/promises"in another for the same purpose creates inconsistency. Choose one approach per concern and stick with it throughout the project. -
Assuming
__dirnameand__filenameexist. These are Node-isms that do not exist in standard ESM. Useimport.meta.dirnameandimport.meta.filename(Deno 1.40+) or construct paths fromimport.meta.url. -
Forgetting that Deno caches remote modules globally. Once a URL-imported module is cached, Deno uses the cached version indefinitely. After upstream changes, you must run
deno cache --reloadto force a re-download. Stale caches can cause hours of debugging.
Best Practices
- Pin dependency versions in import URLs or use an import map in
deno.jsonso builds are reproducible. - Use JSR (
jsr:) as the primary registry; it offers type-checked publishing and is Deno-native. - Scope permissions narrowly — grant
--allow-read=./datainstead of blanket--allow-read. - Use
deno.lock(generated automatically) and commit it to version control for deterministic installs. - Prefer Web Standard APIs (
fetch,Request,Response,crypto,ReadableStream) — they are built in and portable. - Run
deno fmtanddeno lintin CI; they require zero configuration.
Common Pitfalls
- Forgetting permissions flags — your script will throw a
PermissionDeniederror at runtime, not at import time. Test with the same flags you'll use in production. - Using bare specifiers without an import map —
import "lodash"will fail unless you map it indeno.jsonimports or prefix withnpm:. - Caching surprises — Deno caches remote modules globally. Run
deno cache --reload mod.tsto force re-download after upstream changes. - Mixing
Deno.*APIs with Node APIs — prefer one or the other. Using both leads to confusing permission and compatibility issues. - Assuming
__dirnameand__filenameexist — these are Node-isms. Useimport.meta.dirnameandimport.meta.filenamein Deno 1.40+ orimport.meta.urlwithURLfor older versions.
Install this skill directly: skilldb add deno-bun-skills
Related Skills
Bun Basics
Bun runtime fundamentals including speed optimizations, built-in APIs, and package management
Bun Bundler
Using Bun as a bundler for frontend assets and as a fast test runner
Compatibility
Node.js compatibility layers in Deno and Bun for running existing npm packages and Node APIs
Deno Deploy
Deno Deploy edge functions for globally distributed serverless applications
Elysia Bun
Elysia web framework on Bun for type-safe, high-performance HTTP APIs
Fresh Framework
Fresh full-stack web framework for Deno with islands architecture and zero client JS by default