Use Cases (Updated: 6/2/2026)

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.

Deno TypeScript Development with Claude Code: Permissions, Tasks, and Deno.serve

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. Create deno.json with dev, start, fmt, lint, test, and check tasks. Do not use -A. Grant only --allow-net=127.0.0.1:8000, --allow-read=./data, and --allow-write=./data. Write tests with Deno.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

  1. Internal API prototypes: admin helpers, webhook receivers, and JSON-backed demos can start with Deno.serve before you commit to a larger framework.

  2. Repository automation: content checks, config validation, and API snapshots fit well in deno task without adding a large Node toolchain.

  3. Team onboarding: Deno permission errors teach beginners why a script needs network, read, or write access. That makes Claude Code reviews more concrete.

  4. Small edge-oriented services: using Web-standard Request and Response keeps 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.

#Claude Code #Deno #TypeScript #runtime #security
Free

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.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.