| 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/