Deno TypeScript Development with Claude Code: Permissions, Tasks, and Deno.serve
Build Deno apps with Claude Code using least-privilege permissions, deno.json tasks, Deno.serve, and built-in fmt/lint/test.
Start Deno Work with the Permission Model
Deno is a JavaScript and TypeScript runtime. A runtime is the layer that actually executes your code. The important difference for day-to-day work is that Deno is secure by default: file access, network access, environment variables, and subprocess execution are not open unless you grant them. The official Deno security and permissions guide is the page to keep beside Claude Code when you review generated commands.
Claude Code is useful for Deno because it can create the server, tests, deno.json, and review checklist in one pass. The risk is that a vague request often produces -A, Node-style assumptions, or unnecessary tooling. Tell Claude Code the runtime, the API surface, the denied shortcuts, and the exact verification command.
Useful beginner terms: a permission is the allow-list for what a program may touch, a deno task is a named command in deno.json, a formatter rewrites style, a linter detects risky patterns, and a test runner executes Deno.test() tests. Deno ships with these tools, so a small project can stay lean.
For adjacent Claude Code work, see API development, testing strategies, and TypeScript tips.
Prompt Claude Code with Constraints
Use a prompt like this:
Build a small Deno JSON API. Use
Deno.serve, not Express. Createdeno.jsonwithdev,start,fmt,lint,test, andchecktasks. Do not use-A. Grant only--allow-net=127.0.0.1:8000,--allow-read=./data, and--allow-write=./data. Write tests withDeno.test().
That wording anchors the output to official Deno concepts: configuration, deno task, Deno.serve, and the built-in test runner.
Project Tasks
{
"tasks": {
"dev": "deno run --watch --allow-net=127.0.0.1:8000 --allow-read=./data --allow-write=./data server.ts",
"start": "deno run --allow-net=127.0.0.1:8000 --allow-read=./data --allow-write=./data server.ts",
"fmt": "deno fmt",
"lint": "deno lint",
"test": "deno test",
"check": "deno fmt --check && deno lint && deno test"
},
"fmt": {
"lineWidth": 100,
"semiColons": true
},
"lint": {
"rules": {
"tags": ["recommended"]
}
},
"imports": {
"@std/assert": "jsr:@std/assert"
}
}
deno fmt, deno lint, and deno test are built in. The check task gives Claude Code and CI one command to run after edits.
Copy-Paste API Example
// app.ts
export type Item = {
id: string;
title: string;
done: boolean;
};
export interface ItemStore {
list(): Promise<Item[]>;
save(items: Item[]): Promise<void>;
}
export class FileItemStore implements ItemStore {
constructor(private readonly path = "./data/items.json") {}
async list(): Promise<Item[]> {
try {
const text = await Deno.readTextFile(this.path);
return JSON.parse(text) as Item[];
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
return [];
}
throw error;
}
}
async save(items: Item[]): Promise<void> {
await Deno.writeTextFile(this.path, JSON.stringify(items, null, 2));
}
}
export function createHandler(store: ItemStore): (request: Request) => Promise<Response> {
return async (request: Request): Promise<Response> => {
const url = new URL(request.url);
if (url.pathname === "/health") {
return Response.json({ ok: true });
}
if (url.pathname === "/api/items" && request.method === "GET") {
return Response.json(await store.list());
}
if (url.pathname === "/api/items" && request.method === "POST") {
const body = await request.json().catch(() => null) as { title?: unknown } | null;
if (!body || typeof body.title !== "string" || body.title.trim() === "") {
return Response.json({ error: "title is required" }, { status: 400 });
}
const items = await store.list();
const item: Item = {
id: crypto.randomUUID(),
title: body.title.trim(),
done: false
};
await store.save([...items, item]);
return Response.json(item, { status: 201 });
}
return new Response("Not Found", { status: 404 });
};
}
// server.ts
import { createHandler, FileItemStore } from "./app.ts";
Deno.serve(
{ hostname: "127.0.0.1", port: 8000 },
createHandler(new FileItemStore())
);
mkdir -p data
printf "[]\n" > data/items.json
deno task dev
curl http://127.0.0.1:8000/health
curl -X POST http://127.0.0.1:8000/api/items \
-H "content-type: application/json" \
-d '{"title":"Deno article draft"}'
curl http://127.0.0.1:8000/api/items
Test Without Extra Permissions
// app_test.ts
import { assertEquals } from "@std/assert";
import { createHandler, type Item, type ItemStore } from "./app.ts";
class MemoryStore implements ItemStore {
private items: Item[] = [];
async list(): Promise<Item[]> {
return [...this.items];
}
async save(items: Item[]): Promise<void> {
this.items = [...items];
}
}
Deno.test("GET /health returns ok", async () => {
const handler = createHandler(new MemoryStore());
const response = await handler(new Request("http://localhost/health"));
assertEquals(response.status, 200);
assertEquals(await response.json(), { ok: true });
});
Deno.test("POST /api/items creates an item", async () => {
const handler = createHandler(new MemoryStore());
const response = await handler(
new Request("http://localhost/api/items", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ title: "Write article" })
})
);
const created = await response.json() as Item;
assertEquals(response.status, 201);
assertEquals(created.title, "Write article");
assertEquals(created.done, false);
});
Deno.test("POST /api/items rejects an empty title", async () => {
const handler = createHandler(new MemoryStore());
const response = await handler(
new Request("http://localhost/api/items", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ title: "" })
})
);
assertEquals(response.status, 400);
});
Use Cases
-
Internal API prototypes: admin helpers, webhook receivers, and JSON-backed demos can start with
Deno.servebefore you commit to a larger framework. -
Repository automation: content checks, config validation, and API snapshots fit well in
deno taskwithout adding a large Node toolchain. -
Team onboarding: Deno permission errors teach beginners why a script needs network, read, or write access. That makes Claude Code reviews more concrete.
-
Small edge-oriented services: using Web-standard
RequestandResponsekeeps the code portable, but always confirm deployment constraints in the official docs.
Pitfalls to Avoid
Do not leave -A in generated tasks. It opens every permission and removes Deno’s main safety advantage.
Do not add Express, Jest, Prettier, and ESLint before trying the built-in tools. Deno’s own formatter, linter, and test runner are enough for many small services.
Do not forget to create data/items.json before the file-backed example runs. If you ask Claude Code to create missing directories in code, review the extra write permission carefully.
Do not make every test touch real files or sockets. Inject a memory store for unit tests, then reserve file and network checks for integration tests.
Do not copy old import examples blindly. Deno docs evolve, so check the official pages before publishing or teaching a pattern.
CTA and Verification Note
If this becomes a recurring workflow, put the commands and permission policy in CLAUDE.md, then run deno task check after every Claude Code edit. Start with the free cheatsheet, use the products and templates when you want reusable prompts, and use training and consultation for team rollout.
Hands-on check: create data/items.json, run deno task dev, POST one item with curl, then run deno task check. In review, prioritize removing -A, narrowing read/write paths, and keeping unit tests permission-free.
Extra Production Review
Before using Deno in a team, ask Claude Code to write a permission matrix. Each task should list required read, write, net, env, and run access, plus the reason for each permission. This prevents a gradual slide into broad flags and makes security review easier for non-specialists.
For monetization work, Deno is attractive when you need small APIs, webhook receivers, and internal tools that can be reviewed quickly. The win is not just speed; it is a narrow, explainable execution boundary.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Obsidian to CLAUDE.md Workflow: Stop Re-explaining Context
Turn Obsidian working notes into concise CLAUDE.md operating notes that make Claude Code sessions easier to resume.
Claude Code Revenue CTA Routing: Send Articles to PDF, Gumroad, and Consultation
A Claude Code workflow for routing article readers to the free PDF, Gumroad products, or consultation by intent.
Claude Code Team Handoff Rules: Review Evidence, Permissions, Rollback, and Revenue Paths
A practical Claude Code handoff format for team review, proof, permission rules, rollback, free PDF, Gumroad, and consultation paths.
Related Products
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.