2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Denoってなんだ?〜Node.jsの生みの親が「後悔」を糧に作った次世代ランタイム〜

2
Posted at

この記事の対象読者

  • JavaScriptまたはTypeScriptの基本文法を理解している方
  • Node.jsを使った経験がある、または名前を聞いたことがある方
  • 「なんか新しいランタイムがあるらしい」と気になっている方

この記事で得られること

  • Denoがなぜ生まれたのか、その背景と歴史の理解
  • Denoの特徴とNode.jsとの違いの把握
  • Denoで実際にプロジェクトを作成し、コードを動かせるスキル
  • Node.jsプロジェクトをDenoで動かす方法

この記事で扱わないこと

  • JavaScriptの基礎文法
  • Node.jsの詳細な使い方
  • Deno Deployなどのクラウドサービス

1. Denoとの出会い

「このNode.jsプロジェクト、依存関係多すぎない...?」

node_modulesフォルダを開いて、その中身の量に絶句した経験はありませんか?
私は何度もあります。
たった1つのライブラリを入れただけなのに、いつの間にか数百のパッケージがインストールされている。
「これ、本当に全部必要なの?」という疑問を抱えながら開発を続けていました。

ある日、Denoという新しいランタイムの存在を知りました。
驚いたのは、その開発者がNode.jsと同じ人物だったことです。
Ryan Dahl氏。Node.jsを2009年に作り、JavaScriptをサーバーサイドに持ち込んだ張本人。
その彼が「Node.jsには後悔がある」と語り、Denoを作ったというのです。

「え、自分で作ったものを否定するの?」と最初は驚きました。
しかし、その理由を知れば知るほど、納得せざるを得ませんでした。

Denoは「Node.jsのアナグラム」です。
No-deをひっくり返してDe-no。
これは単なる遊び心ではなく、「Node.jsの反省を活かした新しいランタイム」という意志の表れです。

ここまでで、Denoが「ただの新しいツール」ではないことが伝わったでしょうか。
次は、この記事で使う用語を整理しておきましょう。


2. 前提知識の確認

本題に入る前に、この記事で登場する用語を確認します。

2.1 ランタイムとは

プログラムを実行するための環境のことです。
JavaScriptはブラウザで動く言語でしたが、ランタイムがあればブラウザの外でも動きます。
Node.jsやDenoがサーバーサイドJavaScriptのランタイムです。

2.2 V8エンジンとは

GoogleがChrome用に開発したJavaScript実行エンジンです。
非常に高速で、Node.jsもDenoもこのV8を採用しています。
エンジンは同じでも、その「外側」の設計が異なるのがポイントです。

2.3 TypeScriptとは

JavaScriptに「型」を追加した言語です。
変数や関数の引数に型を指定することで、バグを事前に防げます。
Node.jsでは別途コンパイルが必要ですが、Denoはそのまま実行できます。

2.4 ESモジュール(ESM)とは

JavaScriptの公式モジュールシステムです。
import/export構文を使ってコードを分割・再利用できます。
Node.jsが採用していたCommonJS(require/module.exports)の後継です。

2.5 npmとは

Node Package Managerの略で、JavaScriptのパッケージ管理システムです。
250万以上のパッケージが公開されている、世界最大級のレジストリです。

これらの用語が押さえられたら、いよいよDenoの歴史を見ていきましょう。


3. Denoが生まれた背景

3.1 Ryan Dahlの「10の後悔」

2018年6月、JSConf EUでRyan Dahl氏は衝撃的なプレゼンを行いました。
タイトルは「10 Things I Regret About Node.js」。
Node.jsの生みの親が、自らの創造物に対する後悔を公開したのです。

彼が挙げた主な後悔は以下の通りです。

後悔 内容
Promiseを採用しなかった 非同期処理がコールバック地獄になった
セキュリティを軽視した ファイルやネットワークに無制限アクセス可能
GYPビルドシステム 複雑でメンテナンスが困難
package.json 不必要に複雑な依存管理
node_modules 深いネスト構造と肥大化
拡張子の省略 require('module').jsを省略可能にした
index.js 暗黙的なエントリーポイント

3.2 Node.jsの設計上の問題

Node.jsは2009年に生まれました。
当時はJavaScriptの標準モジュールシステム(ESM)が存在せず、CommonJSを採用しました。
また、サーバーサイドでの利用を前提に、ファイルシステムやネットワークへのアクセスを自由にしました。

これらの決定は当時としては合理的でした。
しかし、15年経った今、以下の問題が顕在化しています。

セキュリティの問題: あなたがインストールした何気ないnpmパッケージ。
そのパッケージ(と、その依存先の依存先の...)は、あなたのファイルを読み放題、ネットワークにアクセスし放題です。

依存関係の爆発: 1つのパッケージを入れると、数百の依存がついてくることも珍しくありません。
有名な話として、is-odd(奇数判定)がis-numberに依存し、それがさらに別のパッケージに依存する...という連鎖があります。

3.3 Denoの誕生

このプレゼンの最後に、Ryan Dahl氏は新しいプロジェクトを発表しました。
それがDenoです。

Denoは以下の設計思想で作られています。

  • セキュアバイデフォルト: 明示的に許可しない限り、何もできない
  • TypeScriptネイティブ: 設定なしでTypeScriptを直接実行
  • Web標準API: ブラウザと同じAPIを使える
  • 単一バイナリ: 依存関係なしで動く単一の実行ファイル

2020年5月13日、Deno 1.0が正式リリースされました。
そして2024年10月9日、待望のDeno 2.0がリリース。
Node.jsとnpmとの完全な後方互換性が実現しました。

背景がわかったところで、Denoの具体的な特徴を見ていきましょう。


4. Denoの基本概念

4.1 セキュアバイデフォルト

Denoの最大の特徴は「デフォルトで何もできない」ことです。

Node.jsでは、実行したスクリプトはあらゆるファイルを読み書きし、ネットワークに接続できました。
Denoでは、明示的にフラグを指定しない限り、これらの操作は拒否されます。

// このコードを実行しても、権限がなければエラーになる
const data = await Deno.readTextFile("./secret.txt");

実行時に以下のようなプロンプトが表示されます。

┌ ⚠️  Deno requests read access to "./secret.txt".
├ Requested by `Deno.readTextFile()` API.
├ Learn more at: https://docs.deno.com/go/--allow-read
├ Run again with --allow-read to bypass this prompt.
└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions) >

これは「最小権限の原則」を体現しています。
人間で言えば、「必要な鍵だけ渡す」ようなものです。
全ての鍵を渡すのではなく、本当に必要な部屋の鍵だけを渡す。

4.2 権限フラグの一覧

フラグ 許可する内容
--allow-read ファイル読み取り --allow-read=./data
--allow-write ファイル書き込み --allow-write=./logs
--allow-net ネットワークアクセス --allow-net=api.example.com
--allow-env 環境変数の読み取り --allow-env=API_KEY
--allow-run サブプロセスの実行 --allow-run=git
--allow-ffi 外部ライブラリの呼び出し FFI使用時
-A 全ての権限を許可 開発時のみ推奨

スコープを限定することで、万が一悪意のあるコードが混入しても被害を最小化できます。

4.3 TypeScriptファーストクラスサポート

Node.jsでTypeScriptを使うには、以下の手順が必要でした。

  1. typescriptパッケージをインストール
  2. tsconfig.jsonを設定
  3. tscでコンパイル
  4. コンパイル結果の.jsファイルを実行

Denoでは、.tsファイルをそのまま実行できます。

# Node.jsの場合
npm install typescript
npx tsc main.ts
node main.js

# Denoの場合
deno run main.ts

設定ファイルも型定義ファイルも不要。
「書いたら動く」シンプルさがDenoの魅力です。

4.4 Web標準APIの採用

DenoはブラウザのWeb APIを可能な限り採用しています。
fetchRequestResponseURLWebSocketなどがそのまま使えます。

// ブラウザと同じfetch APIが使える
const response = await fetch("https://api.github.com/users/denoland");
const data = await response.json();
console.log(data.name);

これにより、フロントエンドとバックエンドで同じ知識が活かせます。
「ブラウザで動くコードは、Denoでもほぼそのまま動く」と考えてOKです。

4.5 組み込みツールチェーン

Denoは「バッテリー同梱」の思想を持っています。
Node.jsでは別途インストールが必要だった各種ツールが、最初から組み込まれています。

ツール コマンド Node.jsの場合
フォーマッター deno fmt Prettier
リンター deno lint ESLint
テストランナー deno test Jest, Mocha等
ドキュメント生成 deno doc JSDoc等
バンドラー deno bundle webpack, esbuild等
実行ファイル化 deno compile pkg等
タスクランナー deno task npm scripts

基本概念が理解できたところで、実際に手を動かしてみましょう。


5. 実際に使ってみよう

5.1 環境構築

インストール方法

お使いのOSに合わせて、以下のコマンドを実行してください。

macOS / Linux:

curl -fsSL https://deno.land/install.sh | sh

Windows (PowerShell):

irm https://deno.land/install.ps1 | iex

Homebrew (macOS):

brew install deno

インストール後、以下のコマンドで確認します。

deno --version

実行結果

deno 2.6.1 (stable, release, aarch64-apple-darwin)
v8 13.4.114.9-rusty
typescript 5.7.3

5.2 設定ファイルの準備

以下の内容をdeno.jsonとして保存してください。

{
  "compilerOptions": {
    "strict": true,
    "lib": ["deno.window"]
  },
  "imports": {
    "@std/": "jsr:@std/"
  },
  "tasks": {
    "dev": "deno run --watch --allow-net --allow-read main.ts",
    "test": "deno test --allow-read",
    "lint": "deno lint",
    "fmt": "deno fmt"
  },
  "fmt": {
    "useTabs": false,
    "lineWidth": 80,
    "indentWidth": 2,
    "singleQuote": true,
    "proseWrap": "preserve"
  },
  "lint": {
    "rules": {
      "tags": ["recommended"]
    }
  }
}

各設定の説明は以下の通りです。

項目 説明
compilerOptions TypeScriptコンパイラの設定
imports インポートマップ(エイリアス設定)
tasks npm scriptsに相当するタスク定義
fmt フォーマッターの設定
lint リンターの設定

5.3 Hello World

main.tsファイルを作成します。

/**
 * Deno入門: Hello Worldサンプル
 * 実行方法: deno run main.ts
 */

function greet(name: string): string {
  return `Hello, ${name}! Welcome to Deno.`;
}

const message = greet("World");
console.log(message);

// 現在のDenoバージョンを表示
console.log(`Deno version: ${Deno.version.deno}`);
console.log(`V8 version: ${Deno.version.v8}`);
console.log(`TypeScript version: ${Deno.version.typescript}`);

実行します。

deno run main.ts

実行結果

Hello, World! Welcome to Deno.
Deno version: 2.6.1
V8 version: 13.4.114.9-rusty
TypeScript version: 5.7.3

5.4 HTTPサーバーを作る

Denoの標準機能でHTTPサーバーを作ってみましょう。

server.tsを作成します。

/**
 * シンプルなHTTPサーバー
 * 実行方法: deno run --allow-net server.ts
 */

const PORT = 8000;

function handler(request: Request): Response {
  const url = new URL(request.url);
  
  // ルーティング
  if (url.pathname === "/") {
    return new Response("Hello from Deno Server!", {
      status: 200,
      headers: { "Content-Type": "text/plain; charset=utf-8" },
    });
  }
  
  if (url.pathname === "/api/time") {
    const now = new Date().toISOString();
    return Response.json({ time: now });
  }
  
  // 404 Not Found
  return new Response("Not Found", { status: 404 });
}

console.log(`Server running at http://localhost:${PORT}/`);
Deno.serve({ port: PORT }, handler);

実行します。

deno run --allow-net server.ts

実行結果

Server running at http://localhost:8000/
Listening on http://0.0.0.0:8000/

ブラウザでhttp://localhost:8000/にアクセスすると、メッセージが表示されます。
http://localhost:8000/api/timeにアクセスすると、JSON形式で現在時刻が返ります。

5.5 外部パッケージを使う

Denoでは、JSR(JavaScript Registry)やnpmからパッケージをインポートできます。

# JSRからパッケージを追加
deno add jsr:@std/datetime

# npmパッケージを追加
deno add npm:chalk@5

deno.jsonに依存関係が追加されます。

{
  "imports": {
    "@std/datetime": "jsr:@std/datetime@^0.225.2",
    "chalk": "npm:chalk@5"
  }
}

使用例を作成します。

/**
 * 外部パッケージの使用例
 * 実行方法: deno run --allow-env packages_example.ts
 */
import { format } from "@std/datetime";
import chalk from "chalk";

const now = new Date();
const formatted = format(now, "yyyy-MM-dd HH:mm:ss");

console.log(chalk.green("現在の日時:"));
console.log(chalk.blue(formatted));
console.log(chalk.yellow("Denoで外部パッケージが使えました!"));

5.6 テストを書く

Denoは組み込みのテストランナーを持っています。

math.tsを作成します。

/**
 * 数学ユーティリティ関数
 */
export function add(a: number, b: number): number {
  return a + b;
}

export function multiply(a: number, b: number): number {
  return a * b;
}

export function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error("Division by zero is not allowed");
  }
  return a / b;
}

math_test.tsを作成します。

/**
 * 数学ユーティリティのテスト
 * 実行方法: deno test math_test.ts
 */
import { assertEquals, assertThrows } from "jsr:@std/assert";
import { add, multiply, divide } from "./math.ts";

Deno.test("add: 正の数の加算", () => {
  assertEquals(add(2, 3), 5);
});

Deno.test("add: 負の数の加算", () => {
  assertEquals(add(-2, -3), -5);
});

Deno.test("multiply: 掛け算", () => {
  assertEquals(multiply(4, 5), 20);
});

Deno.test("divide: 正常な割り算", () => {
  assertEquals(divide(10, 2), 5);
});

Deno.test("divide: ゼロ除算でエラー", () => {
  assertThrows(
    () => divide(10, 0),
    Error,
    "Division by zero is not allowed"
  );
});

テストを実行します。

deno test

実行結果

running 5 tests from ./math_test.ts
add: 正の数の加算 ... ok (1ms)
add: 負の数の加算 ... ok (0ms)
multiply: 掛け算 ... ok (0ms)
divide: 正常な割り算 ... ok (0ms)
divide: ゼロ除算でエラー ... ok (0ms)

ok | 5 passed | 0 failed (15ms)

5.7 よくあるエラーと対処法

エラー 原因 対処法
PermissionDenied: read access ファイル読み取り権限がない --allow-readフラグを追加
PermissionDenied: net access ネットワーク権限がない --allow-netフラグを追加
Module not found インポートパスが間違っている 拡張子(.ts)を含めて指定
Cannot find name 'require' CommonJS構文を使っている ESM形式のimportに変更
error: Specifier not found パッケージが見つからない deno addでパッケージを追加

私が最初にハマったのは、requireを使ってエラーになったことです。
Node.js脳でconst fs = require('fs')と書いてしまい、動かない。
「ESMで書く」というルールを忘れていました。

基本的な使い方をマスターしたので、次は応用例を見ていきましょう。


6. 応用例・発展

6.1 Node.jsプロジェクトをDenoで動かす

Deno 2.0からは、既存のNode.jsプロジェクトをそのまま動かせます。

# package.jsonがあるディレクトリで
deno install
deno task dev  # npm run devの代わり

package.jsonnode_modulesをDenoが理解します。
段階的な移行が可能になりました。

6.2 実行ファイルを作成する

JavaScriptをネイティブ実行ファイルにコンパイルできます。

# 現在のOS向け
deno compile --allow-net server.ts

# Windows向けにクロスコンパイル
deno compile --allow-net --target x86_64-pc-windows-msvc server.ts

# macOS向け
deno compile --allow-net --target x86_64-apple-darwin server.ts

Denoがインストールされていない環境でも、生成された実行ファイルを配布できます。

6.3 さらなる学習リソース


7. まとめ

この記事では、Denoについて以下を解説しました。

  1. Denoの誕生背景: Node.js作者の「後悔」から生まれた次世代ランタイム
  2. セキュリティモデル: デフォルトで何もできない、権限明示が必要な設計
  3. TypeScriptサポート: 設定不要でTypeScriptを直接実行
  4. 組み込みツール: フォーマッター、リンター、テストランナーが標準装備
  5. Node.js互換性: Deno 2.0でpackage.jsonとnode_modulesに対応

私の所感

正直に言うと、Denoが「Node.jsを置き換える」かどうかはわかりません。
Node.jsのエコシステムはあまりにも巨大で、簡単には移行できない現実があります。

しかし、新規プロジェクトでDenoを選択する価値は十分にあると感じています。
特に以下の点が魅力的です。

  • ゼロコンフィグ: 設定ファイルの山と格闘しなくていい
  • セキュリティ: 「何も許可しない」からスタートする安心感
  • TypeScript: 「書いたら動く」シンプルさ

Ryan Dahl氏が「後悔」を語れたのは、自分の過去を直視できる強さがあるからだと思います。
その強さから生まれたDenoには、未来のJavaScript開発のヒントが詰まっています。

ぜひ一度、deno runを試してみてください。
Node.jsとは違う、軽やかな開発体験が待っています。


参考文献

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?