Essential Deno TypeScript practices for ALL Deno development: configuration, imports, testing, permissions, and anti-patterns. Read this skill for any Deno project setup, dependency management, or core development work.
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Use this skill for ALL Deno TypeScript development:
node_modules at all costs - reduce supply chain attack surfacetsconfig.json - Deno uses deno.json(c) as the single source of truthdeno check / deno test - do not rely on external tscDeno.*, Web APIs, Fetch, URLPattern) over polyfillsAlways use the strictest possible settings in deno.json:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true
}
}
Use deno.json or deno.jsonc as the single configuration file for:
Complete Configuration Example:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"tasks": {
"dev": "deno run --watch --allow-net --allow-read --allow-env src/main.ts",
"test": "deno test --allow-net --allow-read --allow-env --coverage=coverage src/",
"test:unit": "deno test --allow-net --allow-read --allow-env --coverage=coverage src/",
"test:e2e": "deno test --allow-net --allow-read --allow-env tests/e2e/",
"test:watch": "deno test --allow-net --allow-read --allow-env --watch --fail-fast",
"coverage": "deno coverage coverage --html",
"check": "deno check $(find src -name '*.ts' -not -name '*.sql')",
"lint": "deno lint",
"fmt": "deno fmt"
},
"imports": {
"@/": "./src/",
"@/domain/": "./src/domain/",
"@/infrastructure/": "./src/infrastructure/",
"@/application/": "./src/application/",
"@std/assert": "jsr:@std/assert@^1.0.14",
"@std/fs": "jsr:@std/fs@^1.0.19",
"@std/testing": "jsr:@std/testing@^1.0.15",
"@std/ulid": "jsr:@std/ulid@1",
"zod": "npm:zod@^3.23.8"
},
"exclude": [
"coverage/",
"node_modules/"
],
"lock": true
}
Define these deno task aliases at minimum:
dev - Development with watch modetest, test:watch - Testingcoverage - Generate coverage reportscheck - Type-check all fileslint, fmt - Code qualitydeno.lock to version control--lock=deno.lock --lock-write=false in CIdeno cache --lock=deno.lock --lock-writeCRITICAL: Never use direct JSR/npm imports in source files. All external dependencies MUST be declared in deno.json import map.
Import Order in Source Files:
// 1. Standard library imports (via import map)
import { assertEquals } from "@std/assert";
// 2. Third-party imports (via import map)
import { z } from "zod";
// 3. Internal imports (absolute paths using import map)
import { Agent } from "@/domain/agent.ts";
// 4. Relative imports (only within same module/context)
import { validatePrompt } from "./validation.ts";
Use sources in this order:
jsr: registry (first choice for TypeScript modules)
"@std/assert": "jsr:@std/assert@^1.0.14"
npm: specifier (when needed; prefer ESM-compatible)
"zod": "npm:zod@^3.23.8"
URL imports (rarely needed with import maps)
CRITICAL: Version pin all external imports. No floating @latest in committed code.
{
"imports": {
"zod": "npm:zod@^3.23.8", // GOOD - pinned
"zod": "npm:zod", // BAD - no version
"@std/assert": "jsr:@std/assert@1" // GOOD - pinned
}
}
Use import map aliases for clean internal imports:
{
"imports": {
"@/": "./src/",
"@/domain/": "./src/domain/",
"@/infrastructure/": "./src/infrastructure/"
}
}
// GOOD - Clean, refactor-safe
import { Agent } from "@/domain/agent.ts";
// BAD - Brittle relative paths
import { Agent } from "../../../domain/agent.ts";
Use type-only imports when importing types:
import type { Agent } from "@/domain/agent.ts";
import type { z } from "zod";
Unit Tests - Co-located with Source:
src/
└── domain/
├── agent.ts
└── agent.test.ts # Unit tests next to code
Integration & E2E Tests - Separate Directory:
tests/
├── integration/
│ └── openai_provider.test.ts
└── e2e/
└── workflow.test.ts
Why Co-location:
deno test discoveryNon-Negotiable:
deno test --coverage=coverage
deno coverage coverage --html
Always use explicit AAA (Arrange-Act-Assert):
import { assertEquals } from "@std/assert";
Deno.test("agent should process valid input", () => {
// Arrange
const agent = new Agent({ name: "TestAgent" });
const input = "Hello, world!";
// Act
const result = agent.process(input);
// Assert
assertEquals(result.status, "success");
});
Red-Green-Refactor with fast feedback:
# Watch mode with fail-fast
deno test --watch --fail-fast
# Run specific file
deno test src/domain/agent.test.ts --watch
CRITICAL: All tests must be deterministic.
Test Flakiness Policy:
Use stable seeds and fixtures:
import { FakeTime } from "@std/testing/time";
Deno.test("timer test", () => {
using time = new FakeTime();
// Deterministic time control
time.tick(1000);
});
All test files must end with .test.ts:
agent.test.ts # GOOD
agent_test.ts # BAD
agent.spec.ts # BAD
@std/assert for assertions@std/testing/mock for test doubles@std/testing/time for time controlDefault to minimum required permissions:
# BAD
deno run --allow-all script.ts
# GOOD
deno run --allow-read=./data --allow-net=api.example.com script.ts
--allow-read[=<path>] # File system read
--allow-write[=<path>] # File system write
--allow-net[=<domain>] # Network access
--allow-env[=<var>] # Environment variables
--allow-run[=<program>] # Subprocess execution
/**
* Fetches data from API and caches locally.
*
* Required permissions:
* - --allow-net=api.example.com
* - --allow-read=./cache
* - --allow-write=./cache
*/
export async function fetchData(): Promise<Data> {
// ...
}
// BAD - Hardcoded
const apiKey = "sk-1234";
// GOOD - From environment
const apiKey = Deno.env.get("API_KEY");
if (!apiKey) {
throw new Error("API_KEY required");
}
Run with: deno run --allow-env=API_KEY script.ts
// BAD - Direct JSR/npm imports in source
import { z } from "npm:zod@^3.23.8";
// GOOD - Use import map
import { z } from "zod";
// BAD - Floating versions
"zod": "npm:zod"
// GOOD - Pinned versions
"zod": "npm:zod@^3.23.8"
// BAD - Node.js APIs
const fs = require("fs");
import * as fs from "node:fs";
// GOOD - Deno APIs
await Deno.readTextFile("file.txt");
// BAD - Unnecessary delay
await new Promise(r => setTimeout(r, 100));
// GOOD - Deterministic time
import { FakeTime } from "@std/testing/time";
using time = new FakeTime();
time.tick(100);
# BAD - Overly broad
deno run --allow-all script.ts
# GOOD - Specific
deno run --allow-read=./data script.ts
// BAD - Unnecessary async
async function validate(input: string): Promise<boolean> {
return input.length > 0;
}
// GOOD - Remove async if no await
function validate(input: string): boolean {
return input.length > 0;
}
# Run with watch
deno run --watch src/main.ts
# Type-check
deno check src/**/*.ts
# Format
deno fmt
# Lint
deno lint
# Run all tests
deno test
# With coverage
deno test --coverage=coverage
deno coverage coverage --html
# Watch mode
deno test --watch --fail-fast
# Update dependencies
deno cache --reload
# Update lockfile
deno cache --lock=deno.lock --lock-write
# Run tasks from deno.json
deno task dev
deno task test
deno task coverage
deno.json, never direct imports