name: deno-core description: "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."
Deno Core Best Practices
When to Use This Skill
Use this skill for ALL Deno TypeScript development:
- Setting up new Deno projects
- Writing Deno applications or libraries
- Configuring build, test, and deployment
- Working with dependencies and imports
Core Deno Philosophy
One Tool, Zero Dependencies
- Deno is the only tool you need for TypeScript development
- Built-in tooling: typecheck, lint, format, test, coverage, benchmark
- Avoid
node_modulesat all costs - reduce supply chain attack surface - No need for: tsc, eslint, prettier, jest, vitest, webpack, etc.
TypeScript Excellence
- Strict TypeScript adherence - not just "TS support"
- Bleeding-edge TypeScript features by default - no flags, no config needed
- No compilation step - just run your code
- Target ES2024+ with Stage 3 TC39 proposals
Security First
- Explicit permissions model (no implicit file system or network access)
- Supply chain security through minimal external dependencies
- First-class support for modern security patterns
Language & Compiler
TypeScript Configuration
- Do not use
tsconfig.json- Deno usesdeno.json(c)as the single source of truth - Type-checking powered by
deno check/deno test- do not rely on externaltsc - Default module format is ESM only - no CommonJS interop
- Prefer Deno's runtime-provided types (
Deno.*, Web APIs, Fetch, URLPattern) over polyfills
Strictest Compiler Settings
Always use the strictest possible settings in deno.json:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true
}
}
Configuration & Tasks
deno.json - Single Source of Truth
Use deno.json or deno.jsonc as the single configuration file for:
- Compiler options
- Linting and formatting rules
- Tasks (script aliases)
- Import maps (dependency management)
- Exclusions
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
}
Essential Tasks
Define these deno task aliases at minimum:
dev- Development with watch modetest,test:watch- Testingcoverage- Generate coverage reportscheck- Type-check all fileslint,fmt- Code quality
Lockfile Management
- Always commit
deno.lockto version control - Run with
--lock=deno.lock --lock-write=falsein CI - Update lockfile:
deno cache --lock=deno.lock --lock-write
Imports & Module Resolution
Import Strategy
CRITICAL: 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";
Dependency Source Priority
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)
Version Pinning
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
}
}
Internal Path Aliases
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";
Type-Only Imports
Use type-only imports when importing types:
import type { Agent } from "@/domain/agent.ts";
import type { z } from "zod";
Testing
Test Organization
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:
- Discoverability - tests next to code
- Maintenance - easy to keep in sync
- Deno convention - follows
deno testdiscovery
Coverage Requirements
Non-Negotiable:
- Line coverage: 80%+ - MUST be met
- Branch coverage: 60-80% - MUST be met
deno test --coverage=coverage
deno coverage coverage --html
Test Structure
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");
});
Test Development
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
Deterministic Tests
CRITICAL: All tests must be deterministic.
Test Flakiness Policy:
- Flakiness = highest priority bug
- Never ignore, retry, or "fix" with delays
- Action: investigate, quarantine, fix
- Do NOT merge flaky tests
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);
});
Test File Naming
All test files must end with .test.ts:
agent.test.ts # GOOD
agent_test.ts # BAD
agent.spec.ts # BAD
Testing Tools
- Use
@std/assertfor assertions - Use
@std/testing/mockfor test doubles - Use
@std/testing/timefor time control - Do NOT use Jest - use Deno's built-in runner
Permissions & Security
Principle of Least Privilege
Default to minimum required permissions:
# BAD
deno run --allow-all script.ts
# GOOD
deno run --allow-read=./data --allow-net=api.example.com script.ts
Common Permission Flags
--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
Document Required Permissions
/**
* 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> {
// ...
}
Secrets Management
// 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
Anti-Patterns to Avoid
Import Anti-Patterns
// 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"
Node.js Anti-Patterns
// BAD - Node.js APIs
const fs = require("fs");
import * as fs from "node:fs";
// GOOD - Deno APIs
await Deno.readTextFile("file.txt");
Testing Anti-Patterns
// 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);
Permission Anti-Patterns
# BAD - Overly broad
deno run --allow-all script.ts
# GOOD - Specific
deno run --allow-read=./data script.ts
Async Anti-Patterns
// 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;
}
Quick Command Reference
Development
# Run with watch
deno run --watch src/main.ts
# Type-check
deno check src/**/*.ts
# Format
deno fmt
# Lint
deno lint
Testing
# Run all tests
deno test
# With coverage
deno test --coverage=coverage
deno coverage coverage --html
# Watch mode
deno test --watch --fail-fast
Dependencies
# Update dependencies
deno cache --reload
# Update lockfile
deno cache --lock=deno.lock --lock-write
Tasks
# Run tasks from deno.json
deno task dev
deno task test
deno task coverage
Key Principles Summary
- One tool - Deno replaces tsc, eslint, prettier, jest
- Security first - Explicit permissions, minimal dependencies
- Import maps - All deps in
deno.json, never direct imports - Version pinning - No floating versions
- Co-located tests - Unit tests next to source
- 80%/60% coverage - Line/branch, non-negotiable
- No flakiness - Highest priority, never ignore
- AAA pattern - Explicit in every test
- Least privilege - Minimal permissions
- ESM only - No CommonJS
Additional Resources
- Deno Manual: https://docs.deno.com/
- Deno Standard Library: https://jsr.io/@std
- JSR Registry: https://jsr.io/