Why Deno Needs Permission-First Rules
Deno is secure by default — no file system access, no network access, no environment variables unless explicitly granted with permission flags (--allow-read, --allow-net, --allow-env). AI assistants trained on Node.js generate code that assumes unrestricted access: reading files without --allow-read, making HTTP requests without --allow-net, and accessing process.env without --allow-env. The code runs in development (with --allow-all) and fails in production (with restricted permissions).
Deno also uses a different module system: URL imports and JSR (the JavaScript Registry) instead of node_modules and npm. AI generates require() calls, package.json dependencies, and node_modules imports — none of which are Deno-native. While Deno supports npm: specifiers for Node compatibility, native Deno code uses JSR and URL imports.
These rules target Deno 2.0+ which includes improved Node compatibility. They enforce Deno-native patterns while acknowledging npm interop when needed.
Rule 1: Explicit Permissions — Never --allow-all in Production
The rule: 'Grant only the permissions your app needs: deno run --allow-net=api.example.com --allow-read=./data --allow-env=DATABASE_URL main.ts. Never use --allow-all in production — it defeats Deno's security model. Scope permissions: --allow-net=hostname restricts to specific hosts, --allow-read=./path restricts to specific directories, --allow-env=KEY restricts to specific variables.'
For development: '--allow-all is acceptable during development for convenience. But define production permissions in deno.json tasks: { "tasks": { "start": "deno run --allow-net=0.0.0.0:8000,db.example.com --allow-read=./static --allow-env main.ts" } }. This documents exactly what the app needs and serves as the deployment command.'
AI generates deno run main.ts (no permissions — crashes on first I/O) or deno run --allow-all main.ts (no security). The correct approach: minimum permissions scoped to specific hosts, paths, and variables. This is Deno's killer feature — don't disable it.
- --allow-net=host for network — scoped to specific hosts
- --allow-read=./path for filesystem — scoped to specific directories
- --allow-env=KEY for env vars — scoped to specific variables
- --allow-all for development only — never in production
- Document permissions in deno.json tasks — they're the security policy
--allow-all disables Deno's entire security model. Scoped permissions (--allow-net=api.example.com) are your security policy — they document and enforce exactly what your app can access. Don't trade Deno's killer feature for convenience.
Rule 2: JSR, URL Imports, and npm Interop
The rule: 'Use JSR (@std, @oak, @hono) as the primary package source: import { serve } from "jsr:@std/http". Use URL imports for modules not on JSR: import { z } from "https://deno.land/x/zod/mod.ts". Use npm: specifiers for Node packages when no Deno-native alternative exists: import express from "npm:express". Prefer JSR > URL imports > npm: specifiers — in that order.'
For the import map: 'Define import aliases in deno.json: { "imports": { "@/": "./src/", "oak": "jsr:@oak/oak@^17" } }. Use aliases in code: import { Application } from "oak". This is Deno's equivalent of TypeScript paths — cleaner imports without node_modules.'
For dependencies: 'Pin versions in deno.json imports or use deno.lock for lockfile. Run deno cache to pre-download dependencies. Deno caches modules globally — no node_modules directory, no install step. Run deno outdated to check for updates.'
Rule 3: Deno.serve for HTTP Servers
The rule: 'Use Deno.serve for HTTP servers — it's the built-in, high-performance server: Deno.serve({ port: 8000 }, (request: Request) => new Response("Hello")); Deno.serve uses Web Standard Request/Response — no framework needed for simple APIs. For larger apps, use Oak (@oak/oak) or Hono (jsr:@hono/hono) — both designed for Deno.'
For Web Standard patterns: 'Deno.serve handlers are standard fetch handlers — the same pattern as Cloudflare Workers, Service Workers, and Bun.serve. Skills transfer directly. Use URL for routing: const url = new URL(request.url); if (url.pathname === "/api/users") { ... }. Use request.json() for body, request.headers for headers.'
AI generates http.createServer (Node) or express() in Deno. Deno.serve is simpler, faster, and Deno-native. For routing beyond a few endpoints, use Oak or Hono — not Express (it works via npm: specifier but isn't optimized for Deno).
- Deno.serve for HTTP — Web Standard Request/Response — no framework for simple APIs
- Oak or Hono for larger apps — designed for Deno, not Node ports
- URL for routing — request.json() for body — request.headers for headers
- Same pattern as Workers/Bun — skills transfer across runtimes
- Never http.createServer or express() — use Deno-native server
Deno.test is built into the runtime. deno fmt, deno lint, deno check — all built in. No npm install jest, no npx prettier, no tsc. The entire development toolchain ships with the runtime. Zero dependencies for tooling.
Rule 4: Standard Library (@std)
The rule: 'Use Deno's standard library (@std) for common utilities — it's audited, maintained, and designed for Deno. @std/fs for filesystem operations, @std/path for path manipulation, @std/testing for test utilities, @std/dotenv for .env loading, @std/crypto for hashing, @std/encoding for base64/hex. Import from JSR: import { ensureDir } from "jsr:@std/fs".'
For testing: 'Use Deno's built-in test runner: Deno.test("name", async () => { ... }). Use @std/testing assertions: assertEquals, assertThrows, assertRejects. Use @std/testing/bdd for describe/it syntax. Run tests: deno test. Coverage: deno test --coverage. No test framework installation needed — it's built into the runtime.'
For formatting and linting: 'deno fmt for formatting (built-in, opinionated, zero config). deno lint for linting (built-in, fast, recommended rules by default). deno check for type checking (TypeScript compiler built into Deno). Configure in deno.json: { "fmt": { "lineWidth": 100 }, "lint": { "rules": { "exclude": ["no-unused-vars"] } } }.'
- @std/fs, @std/path, @std/testing, @std/crypto, @std/encoding — audited, maintained
- Deno.test for tests — assertEquals, assertThrows from @std/testing
- deno test for running — deno test --coverage for coverage
- deno fmt for formatting — deno lint for linting — deno check for types
- All built into the runtime — no separate tool installation
Rule 5: Deno Deploy and Production Patterns
The rule: 'Use Deno Deploy for serverless deployment: push to GitHub, Deno Deploy builds and deploys automatically. Deno Deploy runs V8 isolates (like Cloudflare Workers) at 35+ edge locations. Use Deno KV for key-value storage: const kv = await Deno.openKv(); await kv.set(["users", id], user); const result = await kv.get(["users", id]).'
For Deno KV: 'Deno KV is built into the runtime — no external service needed. Works locally (backed by SQLite) and on Deno Deploy (backed by FoundationDB). Atomic operations: await kv.atomic().check({ key, versionstamp }).set(["key"], value).commit(). Use for: sessions, cache, counters, feature flags.'
For production config: 'Use deno.json for all configuration: compilerOptions (TypeScript), imports (dependencies), tasks (scripts), fmt (formatting), lint (linting). Set permissions in deployment configuration — Deno Deploy respects the permissions you specify. Use environment variables via Deno.env.get("KEY") with --allow-env in permissions.'
Deno KV is a key-value database built into the runtime — works locally (SQLite) and on Deno Deploy (FoundationDB). Atomic operations, no external service, no connection management. Import nothing — just await Deno.openKv().
Complete Deno Rules Template
Consolidated rules for Deno projects.
- Explicit permissions: --allow-net=host, --allow-read=path, --allow-env=KEY — never --allow-all in prod
- JSR > URL imports > npm: specifiers — import map in deno.json for aliases
- Deno.serve for HTTP — Web Standard Request/Response — Oak/Hono for larger apps
- @std library for utilities: fs, path, testing, crypto, encoding — audited and maintained
- Deno.test built-in — deno fmt/lint/check built-in — zero tool installation
- Deno KV for key-value — atomic operations — works locally and on Deploy
- Deno Deploy for serverless edge — GitHub push deploys — V8 isolates
- deno.json for all config — permissions documented in tasks — deno.lock for reproducibility