Deno 2完全ガイド2026: Node.js互換・npm対応・TypeScript最適化の最新ランタイム


Deno 2は、Node.jsの作者が開発した次世代JavaScriptランタイムです。本記事では、Node.js完全互換、npm対応、TypeScript最適化など、Deno 2の全機能を実践的なコード例とともに徹底解説します。

Deno 2とは

概要

Deno 2は、セキュアでモダンなJavaScript/TypeScriptランタイムです。

// Deno 2の主な特徴
/**
 * 1. Node.js完全互換 - package.json、node_modules対応
 * 2. npm完全対応 - npm、yarn、pnpmのパッケージを直接使用
 * 3. TypeScript最適化 - トランスパイル不要、高速実行
 * 4. セキュアデフォルト - 明示的な権限管理
 * 5. Web標準API - fetchなどのモダンAPIを標準装備
 * 6. 組み込みツール - linter、formatter、テストランナー
 */

Node.jsとの違い

// パフォーマンス比較(ベンチマーク)
/**
 * TypeScript実行速度:
 * - ts-node: 800ms
 * - tsx: 150ms
 * - Deno 2: 50ms(16倍高速)
 *
 * パッケージインストール:
 * - npm install: 25秒
 * - Deno(自動キャッシュ): 0秒
 *
 * セキュリティ:
 * - Node.js: すべての権限が自動付与
 * - Deno: 明示的な権限が必要
 */

Deno 1 vs Deno 2

/**
 * Deno 1の制限:
 * - Node.js互換性が不完全
 * - npm対応が限定的
 * - package.jsonが使えない
 *
 * Deno 2の改善:
 * - Node.js完全互換(99%以上)
 * - npm完全対応
 * - package.json完全サポート
 * - node_modules対応
 * - パフォーマンス大幅向上
 */

インストールとセットアップ

インストール

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

# Homebrew
brew install deno

# Windows(PowerShell)
irm https://deno.land/install.ps1 | iex

# Windows(Scoop)
scoop install deno

# npmから(グローバル)
npm install -g deno

# バージョン確認
deno --version
# deno 2.1.0

# アップグレード
deno upgrade

# 特定バージョンへのアップグレード
deno upgrade --version 2.0.0

エディタ設定(VS Code)

# Deno拡張機能インストール
code --install-extension denoland.vscode-deno
// .vscode/settings.json

{
  "deno.enable": true,
  "deno.lint": true,
  "deno.unstable": false,
  "deno.suggest.imports.hosts": {
    "https://deno.land": true
  },
  "[typescript]": {
    "editor.defaultFormatter": "denoland.vscode-deno"
  },
  "[javascript]": {
    "editor.defaultFormatter": "denoland.vscode-deno"
  }
}

プロジェクト初期化

# 新規プロジェクト作成
mkdir my-deno-project
cd my-deno-project

# deno.jsonを作成
deno init

# package.jsonプロジェクト(Node.js互換モード)
deno init --npm
// deno.json

{
  "tasks": {
    "dev": "deno run --watch main.ts",
    "start": "deno run --allow-net --allow-read main.ts",
    "test": "deno test --allow-all",
    "lint": "deno lint",
    "fmt": "deno fmt"
  },
  "imports": {
    "@std/": "jsr:@std/",
    "express": "npm:express@^4.18.2"
  },
  "compilerOptions": {
    "lib": ["deno.window"],
    "strict": true
  },
  "fmt": {
    "useTabs": false,
    "lineWidth": 100,
    "indentWidth": 2,
    "semiColons": false,
    "singleQuote": true
  },
  "lint": {
    "rules": {
      "tags": ["recommended"]
    }
  }
}

基本的な使い方

ファイル実行

# TypeScriptファイル実行(トランスパイル不要)
deno run main.ts

# JavaScriptファイル実行
deno run main.js

# URLから直接実行
deno run https://deno.land/std/examples/welcome.ts

# ウォッチモード(ホットリロード)
deno run --watch main.ts

# 権限付与
deno run --allow-net --allow-read main.ts

# すべての権限を付与
deno run --allow-all main.ts

# 環境変数を指定
deno run --env=.env main.ts

権限管理

// main.ts

// ネットワークアクセス(--allow-net が必要)
const response = await fetch('https://api.example.com/data')

// ファイル読み込み(--allow-read が必要)
const text = await Deno.readTextFile('./data.txt')

// ファイル書き込み(--allow-write が必要)
await Deno.writeTextFile('./output.txt', 'Hello Deno')

// 環境変数(--allow-env が必要)
const apiKey = Deno.env.get('API_KEY')

// サブプロセス実行(--allow-run が必要)
const process = new Deno.Command('ls', { args: ['-la'] })
const output = await process.output()
# 詳細な権限指定
deno run \
  --allow-net=api.example.com \
  --allow-read=./data \
  --allow-write=./output \
  --allow-env=API_KEY \
  main.ts

# インタラクティブな権限確認
deno run --prompt main.ts
# → 実行時に権限を確認するプロンプトが表示

TypeScript完全サポート

// types.ts - トランスパイル不要で直接実行

// 最新のTypeScript機能をすべてサポート
type User = {
  id: number
  name: string
  email: string
}

// Decorators(Stage 3)
function log(target: any, key: string) {
  console.log(`Called ${key}`)
}

class UserService {
  @log
  async getUser(id: number): Promise<User> {
    const response = await fetch(`https://api.example.com/users/${id}`)
    return response.json()
  }
}

// Type-only imports
import type { ServerOptions } from './server.ts'

// satisfies operator
const config = {
  host: 'localhost',
  port: 3000,
} satisfies ServerOptions

// using declaration(自動リソース管理)
{
  using file = await Deno.open('./data.txt')
  // ブロックを抜けると自動的にファイルがクローズされる
}

npmパッケージの使用

npm: スキーマ

// npm-import.ts

// npmパッケージを直接インポート
import express from 'npm:express@4'
import { z } from 'npm:zod@3'
import chalk from 'npm:chalk@5'

const app = express()

app.get('/', (req, res) => {
  res.send(chalk.blue('Hello from Deno + Express!'))
})

app.listen(3000, () => {
  console.log(chalk.green('Server running on http://localhost:3000'))
})

// Zodでバリデーション
const userSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  age: z.number().min(0),
})

type User = z.infer<typeof userSchema>

const user: User = {
  name: 'Alice',
  email: 'alice@example.com',
  age: 30,
}

console.log(userSchema.parse(user))
# 実行(npmパッケージは自動的にキャッシュされる)
deno run --allow-net --allow-read --allow-env npm-import.ts

package.json対応

// package.json

{
  "name": "my-deno-app",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "deno run --watch --allow-all src/main.ts",
    "start": "deno run --allow-all src/main.ts",
    "test": "deno test --allow-all"
  },
  "dependencies": {
    "express": "^4.18.2",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/express": "^4.17.21"
  }
}
// src/main.ts

// package.jsonのdependenciesから自動的にインポート
import express from 'express'
import { z } from 'zod'

const app = express()

app.use(express.json())

const userSchema = z.object({
  name: z.string(),
  email: z.string().email(),
})

app.post('/users', (req, res) => {
  try {
    const user = userSchema.parse(req.body)
    res.json({ success: true, user })
  } catch (error) {
    res.status(400).json({ error: error.errors })
  }
})

app.listen(3000)
# node_modules のインストール(オプション)
deno install

# 実行(package.jsonが自動認識される)
deno task dev

Node.js組み込みモジュール

// node-modules.ts

// Node.js標準モジュールを使用
import { readFile, writeFile } from 'node:fs/promises'
import { createServer } from 'node:http'
import { join } from 'node:path'
import crypto from 'node:crypto'

// ファイル操作
const data = await readFile('./data.txt', 'utf-8')
await writeFile('./output.txt', data.toUpperCase())

// HTTPサーバー
const server = createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' })
  res.end('Hello from Node.js HTTP module in Deno!')
})

server.listen(3000)

// 暗号化
const hash = crypto.createHash('sha256')
hash.update('password')
console.log(hash.digest('hex'))

// パス操作
const fullPath = join(import.meta.dirname!, 'data', 'users.json')
console.log(fullPath)

Web標準API

Fetch API

// fetch-api.ts

// GET リクエスト
const response = await fetch('https://api.example.com/users')
const users = await response.json()

// POST リクエスト
const newUser = {
  name: 'Alice',
  email: 'alice@example.com',
}

const createResponse = await fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(newUser),
})

const created = await createResponse.json()

// エラーハンドリング
try {
  const response = await fetch('https://api.example.com/data')

  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`)
  }

  const data = await response.json()
  console.log(data)
} catch (error) {
  console.error('Fetch error:', error)
}

WebSocket

// websocket-client.ts

const ws = new WebSocket('wss://echo.websocket.org')

ws.onopen = () => {
  console.log('Connected')
  ws.send('Hello WebSocket!')
}

ws.onmessage = (event) => {
  console.log('Received:', event.data)
}

ws.onerror = (error) => {
  console.error('WebSocket error:', error)
}

ws.onclose = () => {
  console.log('Disconnected')
}

ファイルシステムAPI

// file-system.ts

// ファイル読み込み
const text = await Deno.readTextFile('./data.txt')
const bytes = await Deno.readFile('./image.png')

// ファイル書き込み
await Deno.writeTextFile('./output.txt', 'Hello Deno')
await Deno.writeFile('./copy.png', bytes)

// ディレクトリ操作
await Deno.mkdir('./new-dir', { recursive: true })

// ファイル一覧
for await (const entry of Deno.readDir('./')) {
  console.log(entry.name, entry.isFile ? 'file' : 'directory')
}

// ファイル情報
const fileInfo = await Deno.stat('./data.txt')
console.log('Size:', fileInfo.size)
console.log('Modified:', fileInfo.mtime)

// ファイル削除
await Deno.remove('./temp.txt')
await Deno.remove('./temp-dir', { recursive: true })

// ファイルコピー
await Deno.copyFile('./source.txt', './destination.txt')

// ファイルリネーム
await Deno.rename('./old-name.txt', './new-name.txt')

HTTPサーバー

基本的なサーバー

// server.ts

Deno.serve({ port: 3000 }, (req) => {
  const url = new URL(req.url)

  if (url.pathname === '/') {
    return new Response('Hello Deno!', {
      headers: { 'Content-Type': 'text/plain' },
    })
  }

  if (url.pathname === '/json') {
    return Response.json({ message: 'Hello JSON' })
  }

  if (url.pathname === '/users') {
    const users = [
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' },
    ]
    return Response.json(users)
  }

  return new Response('Not Found', { status: 404 })
})
# サーバー起動
deno run --allow-net server.ts

RESTful API

// api-server.ts

type User = {
  id: number
  name: string
  email: string
}

const users: User[] = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' },
]

Deno.serve({ port: 3000 }, async (req) => {
  const url = new URL(req.url)
  const method = req.method

  // CORS設定
  const headers = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
    'Access-Control-Allow-Headers': 'Content-Type',
    'Content-Type': 'application/json',
  }

  if (method === 'OPTIONS') {
    return new Response(null, { headers })
  }

  // GET /api/users
  if (method === 'GET' && url.pathname === '/api/users') {
    return Response.json(users, { headers })
  }

  // GET /api/users/:id
  const userMatch = url.pathname.match(/^\/api\/users\/(\d+)$/)
  if (method === 'GET' && userMatch) {
    const id = parseInt(userMatch[1])
    const user = users.find((u) => u.id === id)

    if (!user) {
      return Response.json({ error: 'User not found' }, {
        status: 404,
        headers,
      })
    }

    return Response.json(user, { headers })
  }

  // POST /api/users
  if (method === 'POST' && url.pathname === '/api/users') {
    const body = await req.json() as Omit<User, 'id'>
    const newUser: User = {
      id: Math.max(...users.map((u) => u.id)) + 1,
      ...body,
    }
    users.push(newUser)

    return Response.json(newUser, { status: 201, headers })
  }

  // PUT /api/users/:id
  if (method === 'PUT' && userMatch) {
    const id = parseInt(userMatch[1])
    const index = users.findIndex((u) => u.id === id)

    if (index === -1) {
      return Response.json({ error: 'User not found' }, {
        status: 404,
        headers,
      })
    }

    const body = await req.json() as Partial<User>
    users[index] = { ...users[index], ...body }

    return Response.json(users[index], { headers })
  }

  // DELETE /api/users/:id
  if (method === 'DELETE' && userMatch) {
    const id = parseInt(userMatch[1])
    const index = users.findIndex((u) => u.id === id)

    if (index === -1) {
      return Response.json({ error: 'User not found' }, {
        status: 404,
        headers,
      })
    }

    users.splice(index, 1)

    return new Response(null, { status: 204, headers })
  }

  return Response.json({ error: 'Not found' }, { status: 404, headers })
})

WebSocketサーバー

// websocket-server.ts

const clients = new Set<WebSocket>()

Deno.serve({ port: 3000 }, (req) => {
  const upgrade = req.headers.get('upgrade') || ''

  if (upgrade.toLowerCase() !== 'websocket') {
    return new Response('Not a websocket request', { status: 400 })
  }

  const { socket, response } = Deno.upgradeWebSocket(req)

  socket.onopen = () => {
    console.log('Client connected')
    clients.add(socket)
  }

  socket.onmessage = (event) => {
    console.log('Received:', event.data)

    // 全クライアントにブロードキャスト
    for (const client of clients) {
      if (client.readyState === WebSocket.OPEN) {
        client.send(`Echo: ${event.data}`)
      }
    }
  }

  socket.onclose = () => {
    console.log('Client disconnected')
    clients.delete(socket)
  }

  socket.onerror = (error) => {
    console.error('WebSocket error:', error)
    clients.delete(socket)
  }

  return response
})

静的ファイル配信

// static-server.ts

import { serveDir } from 'jsr:@std/http/file-server'

Deno.serve({ port: 3000 }, (req) => {
  return serveDir(req, {
    fsRoot: './public',
    urlRoot: '',
    showDirListing: true,
    enableCors: true,
  })
})

データベース

SQLite

// sqlite.ts

import { Database } from 'jsr:@db/sqlite'

// データベース接続
const db = new Database('mydb.sqlite')

// テーブル作成
db.exec(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`)

// データ挿入
const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
insert.run('Alice', 'alice@example.com')
insert.run('Bob', 'bob@example.com')

// データ取得
const getUser = db.prepare('SELECT * FROM users WHERE id = ?')
const user = getUser.get<{ id: number; name: string; email: string }>(1)
console.log(user)

// 全件取得
const allUsers = db.prepare('SELECT * FROM users').all()
console.log(allUsers)

// トランザクション
db.transaction(() => {
  const stmt = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
  stmt.run('Charlie', 'charlie@example.com')
  stmt.run('David', 'david@example.com')
})

// クローズ
db.close()

PostgreSQL

// postgres.ts

import { Client } from 'https://deno.land/x/postgres/mod.ts'

const client = new Client({
  user: 'postgres',
  password: 'password',
  database: 'mydb',
  hostname: 'localhost',
  port: 5432,
})

await client.connect()

// テーブル作成
await client.queryArray(`
  CREATE TABLE IF NOT EXISTS users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL
  )
`)

// データ挿入
await client.queryArray(
  'INSERT INTO users (name, email) VALUES ($1, $2)',
  ['Alice', 'alice@example.com']
)

// データ取得
const result = await client.queryObject<{ id: number; name: string; email: string }>(
  'SELECT * FROM users WHERE id = $1',
  [1]
)

console.log(result.rows[0])

// 全件取得
const allUsers = await client.queryObject('SELECT * FROM users')
console.log(allUsers.rows)

await client.end()

テスト

基本的なテスト

// math.test.ts

import { assertEquals, assertThrows } from 'jsr:@std/assert'

function add(a: number, b: number): number {
  return a + b
}

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

Deno.test('add with negative numbers', () => {
  assertEquals(add(-5, -3), -8)
})

function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error('Division by zero')
  }
  return a / b
}

Deno.test('divide function', () => {
  assertEquals(divide(10, 2), 5)
  assertThrows(() => divide(10, 0), Error, 'Division by zero')
})
# テスト実行
deno test

# 特定のファイルのみ
deno test math.test.ts

# カバレッジ
deno test --coverage=coverage

# カバレッジレポート生成
deno coverage coverage --html

非同期テスト

// async.test.ts

import { assertEquals } from 'jsr:@std/assert'

async function fetchUser(id: number) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
  return response.json()
}

Deno.test('fetch user', async () => {
  const user = await fetchUser(1)
  assertEquals(typeof user.name, 'string')
  assertEquals(typeof user.email, 'string')
})

Deno.test({
  name: 'async test with timeout',
  async fn() {
    await new Promise((resolve) => setTimeout(resolve, 100))
    assertEquals(1 + 1, 2)
  },
  sanitizeResources: false,
  sanitizeOps: false,
})

モック

// mock.test.ts

import { assertEquals } from 'jsr:@std/assert'
import { stub, spy } from 'jsr:@std/testing/mock'

Deno.test('stub example', () => {
  const obj = {
    method: () => 'original',
  }

  using stubMethod = stub(obj, 'method', () => 'mocked')

  assertEquals(obj.method(), 'mocked')
})

Deno.test('spy example', () => {
  const obj = {
    method: (x: number) => x * 2,
  }

  using spyMethod = spy(obj, 'method')

  obj.method(5)
  obj.method(10)

  assertEquals(spyMethod.calls.length, 2)
  assertEquals(spyMethod.calls[0].args, [5])
  assertEquals(spyMethod.calls[1].args, [10])
})

組み込みツール

Linter

# lint実行
deno lint

# 特定のファイルのみ
deno lint src/

# 自動修正
deno lint --fix

# 設定ファイル
# deno.json
{
  "lint": {
    "files": {
      "include": ["src/"],
      "exclude": ["src/generated/"]
    },
    "rules": {
      "tags": ["recommended"],
      "include": ["ban-untagged-todo"],
      "exclude": ["no-unused-vars"]
    }
  }
}

Formatter

# フォーマット実行
deno fmt

# 特定のファイルのみ
deno fmt src/

# チェックのみ(修正しない)
deno fmt --check

# 設定ファイル
# deno.json
{
  "fmt": {
    "files": {
      "include": ["src/"],
      "exclude": ["src/generated/"]
    },
    "options": {
      "useTabs": false,
      "lineWidth": 100,
      "indentWidth": 2,
      "semiColons": false,
      "singleQuote": true,
      "proseWrap": "preserve"
    }
  }
}

ドキュメント生成

# ドキュメント生成
deno doc main.ts

# HTMLドキュメント生成
deno doc --html --name="My Project" main.ts

# JSONドキュメント生成
deno doc --json main.ts > docs.json

デプロイ

Deno Deploy

# Deno Deployにデプロイ
deployctl deploy --project=my-project main.ts

# 環境変数を指定
deployctl deploy --project=my-project --env=.env main.ts
// main.ts(Deno Deploy用)

Deno.serve((req) => {
  return new Response('Hello from Deno Deploy!')
})

Docker

# Dockerfile

FROM denoland/deno:latest

WORKDIR /app

# 依存関係をキャッシュ
COPY deno.json deno.lock ./
RUN deno install

# ソースコードをコピー
COPY . .

# アプリケーション実行
CMD ["deno", "run", "--allow-net", "--allow-read", "main.ts"]
# ビルド
docker build -t my-deno-app .

# 実行
docker run -p 3000:3000 my-deno-app

systemd(Linux)

# /etc/systemd/system/deno-app.service

[Unit]
Description=Deno Application
After=network.target

[Service]
Type=simple
User=deno
WorkingDirectory=/home/deno/app
ExecStart=/home/deno/.deno/bin/deno run --allow-net --allow-read main.ts
Restart=on-failure

[Install]
WantedBy=multi-user.target
# サービス有効化
sudo systemctl enable deno-app
sudo systemctl start deno-app

# ステータス確認
sudo systemctl status deno-app

まとめ

Deno 2は、セキュアでモダンなJavaScript/TypeScriptランタイムです。

主な利点

  • Node.js完全互換(package.json、npm対応)
  • TypeScript最適化(トランスパイル不要、高速)
  • セキュアデフォルト(明示的な権限管理)
  • 組み込みツール(linter、formatter、テストランナー)
  • Web標準API(fetch、WebSocketなど)

適用場面

  • 新規プロジェクト(TypeScript優先)
  • セキュリティが重要なアプリケーション
  • エッジコンピューティング(Deno Deploy)
  • CLIツール・スクリプト

ベストプラクティス

  • 権限を最小限に(セキュリティ)
  • Web標準APIを優先
  • TypeScriptを最大限活用
  • 組み込みツールを使用(linter、formatter)

2026年現在、Deno 2はNode.js完全互換を実現し、既存のNode.jsプロジェクトからの移行も容易になっています。

参考リンク