본문 바로가기
프론트엔드

Deno란?

by 코딩의 세계 2026. 2. 3.

Deno - Node.js의 문제를 근본적으로 해결한 JavaScript 런타임

들어가며

당신이 Node.js 생태계의 문제점을 느껴본 적이 있나요?

  • npm의 의존성 지옥 (node_modules의 쓸데없는 크기)
  • 보안 문제 (패키지가 무분별하게 파일 접근)
  • 모듈 시스템의 혼란 (CommonJS vs ES Modules)
  • TypeScript 설정의 복잡함

Deno는 Node.js의 창시자 Ryan Dahl이 Node.js의 "10년의 후회"를 토대로 만든 새로운 JavaScript 런타임입니다. 처음부터 올바르게 설계되어, 더 안전하고 효율적입니다.

공식 깃허브

https://github.com/denoland/deno/

Node.js vs Deno 비교

핵심 차이점

Node.js (1009년):
├─ CommonJS + ES Modules 혼재
├─ package.json 중앙 집중식
├─ node_modules (비대함)
├─ 기본 보안 없음
├─ 도구 분산됨 (npm, webpack 등)
└─ TypeScript 별도 설정

Deno (2018년, 근본 재설계):
├─ ES Modules만 사용
├─ deno.json (간단함)
├─ 캐싱 (효율적)
├─ 기본 보안 (권한 시스템)
├─ 내장 도구 (Deno fmt, Deno lint 등)
└─ TypeScript 기본 지원

기능 비교

기능 Node.js Deno

JavaScript
TypeScript 별도 설정 필요 ✅ 기본 지원
보안 권한 ✅ 기본 제공
패키지 관리 npm URL 기반
포매터 별도 (Prettier) ✅ deno fmt
린터 별도 (ESLint) ✅ deno lint
테스트 별도 (Jest 등) ✅ deno test
번들러 별도 (webpack 등) ✅ deno bundle
node_modules 거대함 없음
학습곡선 높음 낮음

Deno의 혁신적 기능

1. TypeScript 기본 지원

추가 설정 없이 TypeScript를 기본으로 사용합니다.

# Node.js
npm install -D typescript ts-node @types/node
npx ts-node src/main.ts

# Deno
deno run src/main.ts  # TypeScript 자동 인식!
// main.ts - 그냥 TypeScript로 작성
interface User {
  id: string;
  name: string;
  email: string;
}

async function getUser(id: string): Promise {
  const response = await fetch(`https://api.example.com/users/${id}`);
  return response.json();
}

const user = await getUser('123');
console.log(user.name);

2. 권한 기반 보안

패키지가 무엇을 할 수 있는지 명시적으로 제어합니다.

# 아무 권한도 없음
deno run script.ts

# 파일 접근만 허용
deno run --allow-read script.ts

# 특정 파일만 접근 허용
deno run --allow-read=/tmp script.ts

# 네트워크 접근만 허용
deno run --allow-net script.ts

# 특정 도메인만 접근 허용
deno run --allow-net=api.example.com script.ts

# 환경 변수 접근 허용
deno run --allow-env script.ts

# 특정 환경 변수만 허용
deno run --allow-env=API_KEY script.ts

# 모든 권한 (좋지 않은 예)
deno run --allow-all script.ts
// secure-example.ts
// 파일 읽기
const data = await Deno.readTextFile('./config.json');
// ↑ --allow-read 권한 필요

// 네트워크 요청
const response = await fetch('https://api.example.com/data');
// ↑ --allow-net=api.example.com 권한 필요

// 환경 변수
const apiKey = Deno.env.get('API_KEY');
// ↑ --allow-env=API_KEY 권한 필요

3. URL 기반 모듈 시스템

node_modules가 없습니다. 모듈을 URL로 직접 임포트합니다.

// Node.js
import express from 'express';
import dotenv from 'dotenv';
import { z } from 'zod';

// Deno
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
import { assertEquals } from "https://deno.land/std@0.208.0/testing/asserts.ts";
import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";

장점:

  • node_modules 없음 (디스크 공간 절약)
  • 명시적 버전 관리
  • 직관적인 모듈 탐색

4. deno.json 설정 (간단함)

// deno.json
{
  "name": "my-app",
  "version": "1.0.0",
  
  // 임포트 맵 (import alias)
  "imports": {
    "std/": "https://deno.land/std@0.208.0/",
    "oak": "https://deno.land/x/oak@v12.0.0/mod.ts",
    "@/": "./src/"
  },
  
  // 공개 API 정의
  "exports": {
    ".": "./src/mod.ts",
    "./server": "./src/server.ts"
  },
  
  // 스크립트 (npm scripts와 유사)
  "tasks": {
    "dev": "deno run --allow-net --allow-read src/server.ts",
    "test": "deno test --allow-net --allow-read",
    "fmt": "deno fmt",
    "lint": "deno lint"
  },
  
  // 린트 설정
  "lint": {
    "rules": {
      "tags": ["recommended"],
      "exclude": ["camelcase"]
    }
  },
  
  // 포매터 설정
  "fmt": {
    "semiColons": true,
    "singleQuote": true,
    "indentWidth": 2,
    "lineWidth": 100,
    "trailingComma": "es5"
  }
}

5. 내장 도구

# 포매팅 (Prettier 대체)
deno fmt  # 형식 정렬
deno fmt --check  # 확인만

# 린팅 (ESLint 대체)
deno lint  # 린트 검사

# 테스트 (Jest 대체)
deno test  # 테스트 실행
deno test --coverage  # 커버리지 계산

# 번들링
deno bundle src/main.ts dist/bundle.js  # 번들 생성

# 타입 체크
deno check src/main.ts  # 타입 검증만

프로덕션 레벨 Deno 프로젝트

1. 웹 서버 (Oak Framework)

// src/server.ts
import { Application, Router } from "@/oak";
import { oakCors } from "@/oak_cors";

const app = new Application();
const router = new Router();

// 타입 정의
interface User {
  id: string;
  name: string;
  email: string;
}

// 라우트
router
  .get("/", (ctx) => {
    ctx.response.body = "Welcome to Deno server!";
  })
  .get("/users/:id", async (ctx) => {
    const id = ctx.params.id;
    const user: User = {
      id,
      name: "John Doe",
      email: "john@example.com"
    };
    ctx.response.body = user;
  })
  .post("/users", async (ctx) => {
    const body = ctx.request.body();
    const user = await body.value;
    ctx.response.status = 201;
    ctx.response.body = { ...user, id: crypto.randomUUID() };
  });

// 미들웨어
app.use(oakCors());
app.use(router.routes());
app.use(router.allowedMethods());

// 서버 시작
const PORT = 3000;
console.log(`Server running on http://localhost:${PORT}`);
await app.listen({ port: PORT });
// deno.json
{
  "imports": {
    "oak": "https://deno.land/x/oak@v12.0.0/mod.ts",
    "oak_cors": "https://deno.land/x/cors@v1.2.2/mod.ts",
    "@/": "./src/"
  },
  "tasks": {
    "dev": "deno run --allow-net --allow-read src/server.ts"
  }
}
deno task dev
# Server running on http://localhost:3000

2. 데이터베이스 연결

// src/database.ts
import { Client } from "https://deno.land/x/postgres@v0.17.0/mod.ts";

const client = new Client({
  user: Deno.env.get("DB_USER") || "postgres",
  password: Deno.env.get("DB_PASSWORD") || "postgres",
  hostname: Deno.env.get("DB_HOST") || "localhost",
  port: 5432,
  database: Deno.env.get("DB_NAME") || "myapp",
});

await client.connect();

export interface User {
  id: string;
  name: string;
  email: string;
  created_at: Date;
}

export async function getUser(id: string): Promise {
  const result = await client.queryObject(
    "SELECT * FROM users WHERE id = $1",
    [id]
  );
  return result.rows[0] || null;
}

export async function createUser(name: string, email: string): Promise {
  const result = await client.queryObject(
    "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *",
    [name, email]
  );
  return result.rows[0];
}

3. 마이그레이션 관리

// src/migrations/001_create_users.ts
export const up = `
  CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR NOT NULL,
    email VARCHAR NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT now()
  );
  
  CREATE INDEX idx_users_email ON users(email);
`;

export const down = `
  DROP TABLE users;
`;

// src/migrate.ts
import { Client } from "@/postgres";

interface Migration {
  name: string;
  up: string;
  down: string;
}

async function migrate() {
  const client = new Client({
    /* 설정 */
  });

  await client.connect();

  // 마이그레이션 테이블 생성
  await client.queryArray(`
    CREATE TABLE IF NOT EXISTS migrations (
      id SERIAL PRIMARY KEY,
      name VARCHAR NOT NULL UNIQUE,
      executed_at TIMESTAMP DEFAULT now()
    )
  `);

  // 실행할 마이그레이션
  const migrations: Migration[] = [
    await import("./migrations/001_create_users.ts")
  ];

  for (const migration of migrations) {
    const exists = await client.queryArray(
      "SELECT 1 FROM migrations WHERE name = $1",
      [migration.name]
    );

    if (!exists.rows.length) {
      console.log(`Executing migration: ${migration.name}`);
      await client.queryArray(migration.up);
      await client.queryArray(
        "INSERT INTO migrations (name) VALUES ($1)",
        [migration.name]
      );
    }
  }

  await client.end();
}

if (import.meta.main) {
  await migrate();
}

4. 테스트

// src/utils.test.ts
import { assertEquals, assertThrows } from "std/testing/asserts.ts";
import { add, divide } from "@/utils.ts";

Deno.test("add function", () => {
  assertEquals(add(2, 3), 5);
  assertEquals(add(-1, 1), 0);
});

Deno.test("divide throws on zero", () => {
  assertThrows(() => divide(10, 0), Error, "Cannot divide by zero");
});

// src/api.test.ts
import { assertEquals } from "std/testing/asserts.ts";
import { getUser } from "@/api.ts";

Deno.test("getUser API", async () => {
  // Mock 설정
  const mockUser = { id: "1", name: "John", email: "john@example.com" };
  
  const result = await getUser("1");
  assertEquals(result, mockUser);
});
# 테스트 실행
deno task test

# 커버리지 포함
deno test --coverage=./coverage src/
deno coverage ./coverage

5. 환경 설정

// src/config.ts
export interface Config {
  env: string;
  port: number;
  databaseUrl: string;
  apiKey: string;
  logLevel: string;
}

export function loadConfig(): Config {
  const env = Deno.env.get("NODE_ENV") || "development";

  return {
    env,
    port: parseInt(Deno.env.get("PORT") || "3000"),
    databaseUrl: Deno.env.get("DATABASE_URL") || "",
    apiKey: Deno.env.get("API_KEY") || "",
    logLevel: Deno.env.get("LOG_LEVEL") || "info"
  };
}

// 사용
const config = loadConfig();
console.log(`Running in ${config.env} mode`);
# .env 파일
NODE_ENV=development
PORT=3000
DATABASE_URL=postgresql://user:password@localhost/myapp
API_KEY=your-secret-key
LOG_LEVEL=debug

Node.js에서 Deno로 마이그레이션

1. package.json → deno.json

// package.json (기존)
{
  "dependencies": {
    "express": "^4.18.0",
    "cors": "^2.8.0",
    "dotenv": "^16.0.0",
    "zod": "^3.22.0"
  },
  "devDependencies": {
    "typescript": "^5.0.0",
    "jest": "^29.0.0"
  },
  "scripts": {
    "start": "node src/index.js",
    "test": "jest"
  }
}
// deno.json (Deno)
{
  "imports": {
    "oak": "https://deno.land/x/oak@v12.0.0/mod.ts",
    "cors": "https://deno.land/x/cors@v1.2.2/mod.ts",
    "zod": "https://deno.land/x/zod@v3.22.4/mod.ts",
    "std/": "https://deno.land/std@0.208.0/"
  },
  "tasks": {
    "start": "deno run --allow-net --allow-env --allow-read src/main.ts",
    "test": "deno test --allow-net --allow-env"
  }
}

2. CommonJS → ES Modules

// Node.js (CommonJS)
const express = require('express');
const cors = require('cors');
const { z } = require('zod');

const app = express();
module.exports = { app };
// Deno (ES Modules)
import { Application } from "https://deno.land/x/oak@v12.0.0/mod.ts";
import cors from "https://deno.land/x/cors@v1.2.2/mod.ts";
import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";

export { app };

3. 권한 명시

# 마이그레이션된 스크립트 실행
deno run \
  --allow-net \           # 네트워크 접근
  --allow-read \          # 파일 읽기
  --allow-env \           # 환경 변수
  --allow-write \         # 파일 쓰기
  src/main.ts

팀 협업 가이드

# Deno 프로젝트 온보딩 가이드

## 설치

```bash
# macOS/Linux
curl https://deno.land/x/install/install.sh | sh

# Windows
irm https://deno.land/x/install/install.ps1 | iex

# Homebrew
brew install deno

프로젝트 시작

git clone <repo>
deno cache --reload deno.json
deno task dev

개발 규칙

1. TypeScript 사용

  • 항상 TypeScript로 작성
  • 타입 정의 필수

2. 권한 최소화

  • 필요한 권한만 요청
  • 문서에 권한 명시

3. 내장 도구 사용

deno fmt           # 자동 포매팅
deno lint          # 린트
deno test          # 테스트
deno check         # 타입 검사

4. 의존성 관리

  • deno.json에 import alias 정의
  • 모듈 버전 명시

주의사항

  • npm 패키지 사용 불가 (URL 기반만 가능)
  • Node.js 표준 라이브러리 사용 불가
  • 대신 Deno 표준 라이브러리 (std/) 사용

FAQ

Q: npm 패키지를 사용하고 싶어요. A: npm 레지스트리를 Deno와 호환되도록 하는 서비스(npm:)를 사용할 수 있습니다.

Q: 패키지가 없으면 어떻게 하나요? A: Deno 생태계가 빠르게 성장 중입니다. 필요하면 직접 구현하거나 기여하세요.

## Deno의 미래

Deno는 다음 기능들을 준비 중입니다:

- **npm 호환성 개선**: npm 패키지 직접 사용 가능
- **TypeScript 성능 향상**: 컴파일 캐싱 개선
- **표준 라이브러리 확대**: 더 많은 내장 기능
- **배포 서비스**: Deno Deploy (서버리스)

## 결론

Deno는 **현대 JavaScript 개발의 표준**이 될 것입니다:

✅ **보안**: 명시적 권한 시스템
✅ **단순성**: TypeScript, 포매터, 린터가 모두 내장
✅ **성능**: Node.js보다 빠름
✅ **미래지향적**: ES Modules, URL 기반 모듈
✅ **생산성**: 보일러플레이트 최소화

특히 **신규 프로젝트**나 **마이크로서비스**에 강력히 권장됩니다.

Node.js의 한계를 느꼈다면, **지금 바로 Deno로 전환해보세요!**

'프론트엔드' 카테고리의 다른 글

Giscus란?  (0) 2026.02.06
MSW란?  (0) 2026.02.04
FSD란?  (0) 2026.02.02
Axios로 API 통신 구현하기  (0) 2026.01.11
프론트엔드에서의 프록시  (0) 2026.01.04