deno-pro
Deno runtime guidance — TypeScript-first development, permissions, standard library, and deploying Deno apps.
SKILL.mdskills/deno-pro/SKILL.md
GitHubOverview
Deno is a secure-by-default JavaScript/TypeScript runtime built by the creator of Node.js, designed to fix Node's historical pain points: it ships TypeScript support, a built-in toolchain (formatter, linter, test runner), and URL-based imports instead of a package manager by default. Its permission model — no file, network, or environment access unless explicitly granted — makes it a strong choice for running untrusted or third-party code. This skill covers practical Deno development, dependency management, and deployment.
When to use
- Starting a new TypeScript backend, API, or edge function where you want zero toolchain config.
- Running third-party or generated code and needing sandbox-style permissions.
- Choosing between Deno, Node, and Bun for a project.
- Publishing a library for the Deno ecosystem (JSR) or cross-runtime code.
- Deploying to Deno Deploy or containerizing a Deno service.
- Building CLIs or scripts where a single binary with no install step is valuable.
- Evaluating supply-chain security for JavaScript dependencies.
Core concepts
- Secure by default. Scripts run with no permissions. Grant explicitly with flags (
--allow-net,--allow-read,--allow-env=KEY) or prompt the user. Prefer the narrowest scope:--allow-net=api.example.combeats blanket--allow-net. - TypeScript first-class. No build step: Deno type-checks and runs
.tsdirectly. Note that type checking is skipped with--no-checkfor speed — use it in dev, keep checks in CI. - URL and JSR imports. Dependencies come from URLs or the JSR registry (
jsr:@std/http/file-server). Pin versions in an import map (deno.jsonimports) so builds are reproducible and reviewable. - Built-in toolchain.
deno fmt,deno lint, anddeno testreplace Prettier/ESLint/Jest for most projects. Use them; the zero-config defaults are deliberately opinionated. - Standard library (
@std). JSR's@std/*packages (http, cli, testing, uuid, datetime) are the blessed equivalents of npm staples — prefer them over random URL imports. - Node compatibility. Deno supports
node:specifiers and a growing share of npm packages vianpm:imports andpackage.jsoninterop. It is good but not perfect — test npm deps, especially native ones, before committing. - Fresh and islands architecture. For server-rendered web apps, the Fresh framework ships zero JS to the client by default, hydrating only interactive "islands" — a good fit for content-heavy sites.
- Deno KV. Built-in key-value store (backed by SQLite locally) for sessions, caches, and queues without provisioning a database — ideal for small services and edge deployments.
- Permissions as documentation. The flags a program needs are a readable manifest of what it touches — review them in code review like you would dependency changes.
- Single-binary compile.
deno compilebundles your app into a standalone executable with no runtime install — excellent for CLIs and simple distributions.
Practical workflow
Install and pin the version. Use the official installer and record the version in CI:
deno --version- Pin the exact version in CI config so builds don't drift with new releases.
Initialize the project. Create
deno.jsonwith tasks and an import map:{ "tasks": { "dev": "deno run --watch --allow-net main.ts", "test": "deno test --allow-all" }, "imports": { "@std/http": "jsr:@std/http@1" } }- Keep tasks as the canonical way to run things; document any required env vars alongside.
Write with explicit imports. Use
jsr:/npm:/node:specifiers; avoid bare URLs scattered through code — centralize them in the import map.Develop with permissions in mind. Run with the minimum flags your app needs; when a new permission prompt appears in dev, decide deliberately before adding the flag to the task definition.
- Start restrictive and widen only with justification; record why each permission exists.
Test and lint.
deno testwith@std/testingassertions anddeno lint && deno fmt --checkin CI catch most issues.- Use
deno test --coverageand enforce a coverage floor on critical modules.
- Use
Benchmark hot paths.
deno benchfor micro-benchmarks of parsing, serialization, or crypto code before optimizing blindly.Compile for distribution.
deno compile --allow-net --output myapp main.tsproduces a single binary for CLIs or simple deploys.Deploy. For containers, use the official minimal image and a multi-stage build; for Deno Deploy, push from git. Cache dependencies at build time (
deno cache main.ts) so cold starts stay fast.Bake the permission flags into the container CMD so production can't accidentally run with wider access.
When a permission error appears, run with
--allow-alltemporarily to identify what's needed, then narrow it back down — never ship the broad flags.
# find which permissions a script actually needs deno run --allow-all main.ts # works? now bisect: deno run --allow-net --allow-read main.ts
Common pitfalls
- Granting
--allow-allin production because a permission prompt was annoying — defeats the security model; scope flags per environment. - Unpinned URL imports (
https://deno.land/x/.../mod.ts) that silently upgrade and break builds — always pin with an import map and lockfile (deno.lock). - Assuming npm parity — native modules and deep
node_moduleshacks often fail; verify each npm dependency under Deno before relying on it. - Skipping type checks with
--no-checkeverywhere — fast in dev, but CI must rundeno checkor type errors ship. - Using unstable APIs without the
--unstableflag pinned and documented — they can change between releases. - Forgetting the lockfile in CI —
deno.lockis your reproducibility guarantee; commit it. - Treating
deno.jsontasks as a full build system — for complex pipelines (codegen, multi-service), pair with a real task runner. - Permissions drift — flags accreting over time until the app effectively has
--allow-all; audit the task definitions periodically. - No graceful shutdown — Deno handles SIGTERM, but long-lived connections still need explicit draining in server code.
- Developing against latest, deploying pinned old — version skew between dev and prod causing "works on my machine"; pin everywhere.
- Vendoring ignored — remote-only dependencies breaking air-gapped or flaky-network deploys;
deno vendorfor critical services. - Fresh as a SPA — skipping islands architecture and shipping a JS bundle for static content; Fresh's zero-JS default is the point.