Claude Code로 Deno TypeScript 개발하기: permissions, deno.json, Deno.serve
Claude Code와 Deno로 최소 권한 API를 만드는 실전 가이드. deno.json tasks, Deno.serve, fmt/lint/test 예제를 포함합니다.
Deno는 권한 설계부터 시작한다
Deno는 JavaScript와 TypeScript를 실행하는 runtime입니다. Runtime은 코드를 실제로 실행하는 기반을 뜻합니다. Deno의 중요한 특징은 secure by default입니다. 파일 접근, 네트워크, 환경 변수, 외부 명령 실행은 명시적으로 허용하기 전까지 닫혀 있습니다. 공식 문서는 Security and permissions를 기준으로 보면 됩니다.
Claude Code는 Deno 서버, 테스트, deno.json, 리뷰 체크리스트를 빠르게 만들어 줍니다. 하지만 요청이 모호하면 -A, Node.js식 전제, 불필요한 도구가 섞일 수 있습니다. 그래서 사용할 API, 금지할 선택지, 권한 범위, 검증 명령을 한 번에 적어야 합니다.
초보자용 용어를 정리하면 permission은 프로그램이 접근해도 되는 자원 목록입니다. deno task는 deno.json에 적는 이름 있는 명령입니다. formatter는 코드 형식을 맞추고, linter는 위험한 패턴을 찾고, test runner는 Deno.test()를 실행합니다. Deno는 이 도구들을 기본 제공하므로 작은 프로젝트를 가볍게 시작할 수 있습니다. 관련해서 API 개발, 테스트 전략, TypeScript 팁도 참고하세요.
Claude Code에 줄 프롬프트
Deno로 작은 JSON API를 만들어 주세요. Express가 아니라
Deno.serve를 사용하세요.deno.json에dev,start,fmt,lint,test,checktask를 추가하세요.-A는 금지합니다. 권한은--allow-net=127.0.0.1:8000,--allow-read=./data,--allow-write=./data만 허용하세요. 테스트는Deno.test()로 작성하세요.
이 지시는 공식 configuration, deno task, Deno.serve, deno test 개념에 맞춥니다.
deno.json으로 명령 고정하기
{
"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"
}
}
check는 Claude Code가 수정한 뒤 반드시 통과해야 할 단일 품질 관문입니다.
복사해서 쓰는 Deno.serve API
// 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
추가 권한 없는 테스트
// 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);
});
실무 유스케이스
첫째, 내부 API 프로토타입입니다. 관리자용 헬퍼, webhook 수신기, JSON 데모는 큰 프레임워크 없이 Deno.serve로 시작할 수 있습니다.
둘째, 저장소 자동화입니다. 콘텐츠 검사, 설정 검증, API snapshot 저장은 deno task로 관리하기 좋습니다.
셋째, 팀 온보딩입니다. Deno의 permission error는 네트워크, 읽기, 쓰기 권한이 왜 필요한지 설명하기 쉽습니다.
넷째, 작은 HTTP 서비스를 edge/deploy 환경으로 옮길 가능성이 있을 때입니다. Web 표준 Request와 Response를 쓰면 결합도가 낮습니다.
자주 생기는 함정
-A를 task에 남기지 마세요. 모든 권한을 열어 Deno의 장점을 없앱니다.
Express, Jest, Prettier, ESLint를 먼저 추가하지 마세요. Deno의 기본 도구부터 확인하는 것이 좋습니다.
data/items.json 생성 단계를 빼먹지 마세요. 파일 기반 예제는 초기 파일이 필요합니다.
모든 unit test가 실제 파일이나 socket을 쓰게 만들지 마세요. 메모리 구현이 더 빠르고 권한도 필요 없습니다.
오래된 예제를 그대로 복사하지 마세요. 구현 전 공식 Deno 문서를 다시 확인하세요.
CTA와 검증
이 흐름을 반복한다면 command와 permission policy를 CLAUDE.md에 남기세요. 처음에는 free cheatsheet로 시작하고, 반복 가능한 prompt가 필요하면 products and templates를, 팀 도입에는 training and consultation을 활용하세요.
실습 검증은 data/items.json 생성, deno task dev, curl POST, deno task check 순서로 진행합니다. 리뷰에서는 -A 제거, 경로 제한, 권한 없는 unit test를 가장 먼저 확인합니다.
무료 PDF: Claude Code 치트시트
이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.
개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.
작성자 소개
Masa
Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.
관련 글
Obsidian 메모를 CLAUDE.md로 바꾸는 Claude Code 워크플로
Obsidian 작업 메모를 CLAUDE.md 운영 노트로 정리해 Claude Code 세션의 문맥 반복을 줄입니다.
Claude Code Revenue CTA Routing: 글에서 PDF, Gumroad, 상담으로 보내기
독자 의도에 따라 무료 PDF, Gumroad 상품, 상담으로 나누는 Claude Code CTA 설계입니다.
Claude Code 팀 인계 규칙: 리뷰 증거, 권한, 롤백, 수익 경로까지 넘기는 법
Claude Code 작업을 팀에 넘길 때 필요한 증거, 권한 규칙, 롤백, 무료 PDF, Gumroad, 상담 경로 체크리스트.