The evolution of configuration in modern JavaScript
For a decade, the Node.js ecosystem accumulated configuration sprawl: package.json, tsconfig.json, .eslintrc.json, .prettierrc.json, jest.config.js, and turborepo.json all fighting for authority in your root directory.
Deno 2.0 consolidated this fragmentation into a single canonical manifest: `deno.json` (or deno.jsonc if you prefer comments).
With Deno 2's native support for npm packages, Node.js built-ins (node:fs, node:crypto), monorepo workspaces, and built-in linting/formatting, deno.json replaces half a dozen config files with one clean, type-checked JSON document.
The structure of a complete deno.json
A standard deno.json file controls imports, compiler options, tasks, formatting, and monorepo boundaries:
{
"name": "@acme/core-api",
"version": "2.4.0",
"exports": "./src/index.ts",
"tasks": {
"dev": "deno run --watch --allow-net --allow-env src/main.ts",
"test": "deno test --coverage",
"build": "deno compile --output dist/api src/main.ts"
},
"imports": {
"hono": "jsr:@hono/hono@^4.6.0",
"zod": "npm:zod@^3.24.0",
"@std/json": "jsr:@std/json@^1.0.0",
"@/": "./src/"
},
"compilerOptions": {
"strict": true,
"jsx": "react-jsx",
"jsxImportSource": "react"
},
"fmt": {
"useTabs": false,
"lineWidth": 100,
"indentWidth": 2,
"singleQuote": false
},
"lint": {
"rules": {
"tags": ["recommended"]
}
}
}1. Import Maps: Unifying npm, JSR, and path aliases
Instead of maintaining a separate tsconfig.json for path mapping and package.json for dependencies, the "imports" key in deno.json is a standard Web Import Map:
{
"imports": {
// 1. JSR package (Modern, typed TypeScript registry)
"@std/http": "jsr:@std/http@^1.0.0",
// 2. npm package (With automatic type definitions)
"chalk": "npm:chalk@^5.3.0",
// 3. Path alias (Like Next.js @/* mapping)
"@/components/": "./src/components/",
"@/lib/": "./src/lib/"
}
}Now in any TypeScript file:
import { serve } from "@std/http";
import chalk from "chalk";
import { formatJson } from "@/lib/formatter.ts";
console.log(chalk.green("Server initialized!"));2. Monorepo Workspaces in Deno 2
Deno 2 introduces native monorepo workspace support. Define the member directories in your root deno.json:
{
"workspace": [
"./packages/shared-types",
"./packages/api-gateway",
"./packages/web-client"
]
}In packages/shared-types/deno.json:
{
"name": "@acme/types",
"version": "1.0.0",
"exports": "./index.ts"
}Now api-gateway and web-client can import @acme/types instantly without symlinks or npm link setup.
3. High-Performance JSON handling with @std/json
Deno's standard library ships with @std/json, providing streaming parsers and NDJSON encoders optimized for low memory usage:
import { JsonStringifyStream } from "@std/json/stringify-stream";
import { JsonParseStream } from "@std/json/parse-stream";
// Streaming large NDJSON lines without buffering entire files in memory
const file = await Deno.open("./large-dataset.jsonl");
const recordStream = file.readable
.pipeThrough(new TextDecoderStream())
.pipeThrough(new JsonParseStream());
for await (const record of recordStream) {
console.log("Processed Record ID:", (record as any).id);
}deno.json vs package.json Feature Matrix
| Feature | deno.json | Traditional package.json |
|---|---|---|
| Comments Support | Supported via deno.jsonc | No (Forbidden in pure JSON) |
| Path Aliases | Built-in via "imports" | Requires tsconfig.json |
| Formatting / Linting Rules | Native "fmt" / "lint" | Requires Prettier + ESLint files |
| Workspace Support | Native "workspace" array | Requires pnpm-workspace.yaml |
| Task Runner | Built-in "tasks" | "scripts" key |
Helpful Tools for Deno Developers
- JSON Formatter: Format and pretty-print your
deno.jsonfiles. - JSON Schema Validator: Validate your configuration files against official Deno schemas.
- JSON Lines / NDJSON: Explore and convert newline-delimited JSON datasets used with
@std/json.