6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Bun.jsではじめる高速JavaScript開発ガイド

Posted at

はじめに

Bun.js(以下 Bun)は、JavaScript/TypeScript を高速に実行できる新しいランタイムであり、パッケージマネージャー・バンドラ・テストランナーなどを一体化した「オールインワン開発ツール」です。
Node.js や Deno と同じくサーバーサイド JavaScript を動かせますが、Zig で書かれた実装により非常に高い性能と起動の速さを実現しています。


第1章 Bun.jsとは何か

Bun は「JavaScript ランタイム」「パッケージマネージャー」「バンドラ(ビルドツール)」を一つにまとめた開発環境で、Web アプリやAPI、CLIツールまで幅広い用途に使えるプラットフォームです。
特に開発体験を重視しており、インストールからスクリプト実行、依存関係の管理、TypeScript の取り扱いまでを一貫したコマンドで扱えるのが特徴です。

# Bun のインストール(Linux/macOS)
curl -fsSL https://bun.sh/install | bash

# インストール確認
bun --version

第2章 インストールと初期設定

Bun は公式インストーラースクリプトを用いて一行で導入でき、インストール後は bun コマンドが PATH に追加されます。
プロジェクトを新規作成する際は、bun init を実行すると package.json やエントリーファイルが自動生成され、最小構成のアプリケーションをすぐに試せます。

# 新しいプロジェクトディレクトリ
mkdir my-bun-app
cd my-bun-app

# プロジェクト初期化(対話形式)
bun init

# 作成されたファイルを確認
ls
cat package.json

第3章 基本的なスクリプト実行

Bun は JavaScript だけでなく TypeScript もトランスパイル設定なしで直接実行できるため、小さなスクリプトから本格的なアプリまで同じ感覚で動かせます。
Node.js の node コマンドに相当するのが bun run で、エントリーファイルを指定するだけで即座に実行されるため、学習用の簡単な実験にも向いています。

// hello.ts
const name: string = "Bun.js";
console.log(`こんにちは、${name} の世界!`);
# TypeScript をそのまま実行
bun run hello.ts

第4章 パッケージマネージャーとしてのBun

Bun には npm 互換のパッケージマネージャーが内蔵されており、bun add で依存パッケージをインストールし、bun remove で削除できます。
内部での処理が最適化されているため、一般的な npm や Yarn より高速で、node_modules のインストール時間を大きく短縮できる点が実務でも魅力です。

# パッケージの追加
bun add axios

# 開発依存の追加
bun add -d typescript

# パッケージの削除
bun remove axios

第5章 BunのHTTPサーバー

Bun には組み込みの HTTP サーバー API Bun.serve があり、外部フレームワーク無しでシンプルな Web サーバーや API を構築できます。
fetch ハンドラーに Request を受け取って Response を返す関数を渡すだけで、ルーティングやレスポンス処理の骨格を簡潔に記述できます。

// server.ts
const server = Bun.serve({
  port: 3000,
  fetch(req: Request): Response {
    const url = new URL(req.url);
    if (url.pathname === "/") {
      return new Response("こんにちは Bun サーバー!", {
        status: 200,
        headers: { "Content-Type": "text/plain; charset=utf-8" },
      });
    }
    return new Response("Not Found", { status: 404 });
  },
});

console.log(`Server is running on http://localhost:${server.port}`);
bun run server.ts

第6章 ルーティングとパスパラメータ

Bun の routes 機能やシンプルな条件分岐を利用すると、パスパラメータやワイルドカードを扱うルーティングを自前で構築できます。
より表現力の高いルータを使いたい場合は、mg-bun-routerbun-route などの外部ライブラリを組み合わせ、Express 風の書き味でルート定義を行うこともできます。

// simple-routing.ts
Bun.serve({
  port: 3000,
  fetch(req: Request): Response {
    const url = new URL(req.url);
    const segments = url.pathname.split("/").filter(Boolean);

    if (url.pathname === "/") {
      return new Response("トップページ", { status: 200 });
    }

    if (segments[0] === "users" && segments ) {
      const userId = segments ;
      return Response.json({ id: userId, message: `ユーザー ${userId} のページです` });
    }

    return new Response("ページが見つかりません", { status: 404 });
  },
});

第7章 JSON APIとREST設計

Bun の Response.json や標準 Web API を用いることで、REST 形式の JSON API を簡潔に構築できます。
Request オブジェクトから HTTP メソッドやクエリ、ボディを読み取り、GET/POST/PUT/DELETE などのメソッドごとに適切な処理を分けることで、シンプルながらも拡張しやすい API サーバーを作成できます。

// api.ts
type Todo = { id: number; title: string; done: boolean };

let todos: Todo[] = [
  { id: 1, title: "Bun をインストールする", done: false },
];

Bun.serve({
  port: 3001,
  async fetch(req: Request): Promise<Response> {
    const url = new URL(req.url);

    if (url.pathname === "/todos" && req.method === "GET") {
      return Response.json(todos);
    }

    if (url.pathname === "/todos" && req.method === "POST") {
      const body = await req.json();
      const newTodo: Todo = {
        id: Date.now(),
        title: String(body.title ?? "No title"),
        done: false,
      };
      todos.push(newTodo);
      return Response.json(newTodo, { status: 201 });
    }

    return new Response("Not Found", { status: 404 });
  },
});

第8章 ファイルシステムとユーティリティ

Bun にはファイル読み書きなどの標準ライブラリが備わっており、ログ保存や設定ファイルの管理を追加パッケージ無しで実現できます。
テキストやJSONの読み書きに加えて、バイナリデータにも対応しているため、シンプルな CLI ツールやビルドスクリプトを作る場面でも役立ちます。

// file-io.ts
const path = "./data.txt";

// ファイルへ書き込み
await Bun.write(path, "Bun でファイルを書き込みました。\n");

// ファイルから読み込み
const text = await Bun.file(path).text();
console.log("読み込んだ内容:");
console.log(text);

// JSON ファイルの例
type Config = { appName: string; port: number };

const config: Config = { appName: "my-bun-app", port: 4000 };
await Bun.write("config.json", JSON.stringify(config, null, 2));

const loadedConfig = JSON.parse(await Bun.file("config.json").text()) as Config;
console.log("設定:", loadedConfig);

第9章 TypeScriptサポートと型定義

Bun は TypeScript を標準でサポートしており、追加のトランスパイル設定なしに .ts ファイルを実行できます。
Bun 固有の API を型安全に扱うには @types/bun を開発依存としてインストールし、推奨の tsconfig.json 設定を適用することで、エディタ上でも補完や型チェックを活用できます。

# Bun の型定義を追加
bun add -d @types/bun typescript
// tsconfig.json(一部)
{
  "compilerOptions": {
    "lib": ["ESNext"],
    "target": "ESNext",
    "module": "Preserve",
    "moduleResolution": "bundler",
    "strict": true,
    "jsx": "react-jsx",
    "noEmit": true
  }
}
// version.ts
console.log("Bun version is:", Bun.version);

第10章 バンドラとしてのBunとフロントエンド

Bun の bun build コマンドは、JavaScript や TypeScript を単一ファイルにバンドルし、ブラウザ向けに最適化された出力を生成できます。
従来は Webpack や Rollup が担っていた役割を Bun 単体で置き換えられるため、設定ファイルの数やビルド時間を削減して、よりシンプルなフロントエンド開発を実現できます。

// src/main.ts
const root = document.getElementById("app")!;
root.innerHTML = "<h1>Bun でバンドルしたフロントエンド</h1>";
# フロントエンドコードをバンドル
bun build src/main.ts --outfile=public/bundle.js
<!-- public/index.html -->
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>Bun Frontend</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="./bundle.js"></script>
  </body>
</html>

第11章 環境変数と設定管理

Bun では process.env だけでなく、ビルド時に環境変数を埋め込んだり .env ファイルを読んだりすることで、環境ごとの設定値を切り替えられます。
API キーやデータベース URL などの秘匿情報をコードベースから切り離すことで、セキュリティを保ちつつ開発環境と本番環境を柔軟に切り替えることができます。

# .env ファイル(例)
echo 'API_BASE_URL="https://api.example.com"' > .env
// env-example.ts
const apiBaseUrl = process.env.API_BASE_URL ?? "http://localhost:3000";
console.log("API Base URL:", apiBaseUrl);

// Bun では、環境変数を指定して起動
// API_BASE_URL=https://api.example.com bun run env-example.ts

第12章 外部ルータライブラリによる高度なルーティング

大規模なアプリでは、Express 風の構文でルートを管理できる mg-bun-routerbun-route を利用すると保守性が高まります。
これらのライブラリはパラメータ付きルート、静的ファイル配信、ミドルウェア的な処理を提供し、Bun の高速な HTTP サーバーの上で柔軟なルーティングレイヤーを構築できます。

// router-example.ts
import { Router } from "bun-route";

const router = new Router();

router.get("/", (_req, res) => {
  res.send("ルートパスです");
});

router.get("/hello/:name", (req, res) => {
  const name = req.params.name ?? "ゲスト";
  res.send(`こんにちは、${name} さん!`);
});

const server = Bun.serve({
  fetch: router.handle,
  port: 3000,
});

console.info("Server started:", router.dump(server));

第13章 テストと開発フロー

Bun にはテストランナーも組み込まれており、追加ツール無しでユニットテストを実行できます。
bun test で指定ディレクトリ内のテストファイルをまとめて動かし、ホットリロードや高速起動のおかげで、TDD スタイルの反復開発を快適に進めることができます。

// add.ts
export function add(a: number, b: number): number {
  return a + b;
}
// add.test.ts
import { expect, test } from "bun:test";
import { add } from "./add";

test("add 関数は 1 + 2 = 3 を返す", () => {
  expect(add(1, 2)).toBe(3);
});
# テストの実行
bun test

第14章 パフォーマンスとベンチマークの考え方

Bun は I/O やモジュールロードの最適化により、一般的に Node.js より高速な実行・起動を実現すると紹介されており、特に大量のモジュールを扱うプロジェクトで効果を発揮します。
ただし実際の性能はアプリの構造やボトルネックによって変化するため、自分のユースケースに合わせたベンチマークや計測を行い、CPU 使用率やレスポンスタイムを定期的に確認することが重要です。

// bench.ts
const iterations = 100_000;
console.time("loop");

let sum = 0;
for (let i = 0; i < iterations; i++) {
  sum += i;
}

console.timeEnd("loop");
console.log("sum:", sum);
bun run bench.ts

第15章 Bunで作る小さなフルスタックアプリ

ここまでの要素を組み合わせると、Bun だけでバックエンド API・フロントエンドバンドル・環境変数管理・テストまでを備えた小規模フルスタックアプリを構築できます。
たとえば ToDo 管理アプリであれば、/api/todos を提供する API サーバーと、bun build でバンドルしたフロントエンドを同じプロジェクトで運用し、開発環境ではホットリロード、本番ではシンプルなデプロイ構成を採用できます。

// fullstack/server.ts
type Todo = { id: number; title: string; done: boolean };
let todos: Todo[] = [];

Bun.serve({
  port: 4000,
  async fetch(req: Request): Promise<Response> {
    const url = new URL(req.url);

    if (url.pathname === "/api/todos" && req.method === "GET") {
      return Response.json(todos);
    }

    if (url.pathname === "/api/todos" && req.method === "POST") {
      const body = await req.json();
      const todo: Todo = {
        id: Date.now(),
        title: String(body.title ?? "No title"),
        done: false,
      };
      todos.push(todo);
      return Response.json(todo, { status: 201 });
    }

    if (url.pathname === "/" && req.method === "GET") {
      const html = await Bun.file("public/index.html").text();
      return new Response(html, {
        headers: { "Content-Type": "text/html; charset=utf-8" },
      });
    }

    if (url.pathname === "/bundle.js" && req.method === "GET") {
      const js = await Bun.file("public/bundle.js").text();
      return new Response(js, {
        headers: { "Content-Type": "text/javascript; charset=utf-8" },
      });
    }

    return new Response("Not Found", { status: 404 });
  },
});
// src/frontend.ts
async function loadTodos() {
  const res = await fetch("/api/todos");
  const todos = (await res.json()) as { id: number; title: string; done: boolean }[];
  const list = document.getElementById("list")!;
  list.innerHTML = "";
  for (const t of todos) {
    const li = document.createElement("li");
    li.textContent = `${t.title} (${t.done ? "完了" : "未完了"})`;
    list.appendChild(li);
  }
}

async function addTodo(title: string) {
  await fetch("/api/todos", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ title }),
  });
  await loadTodos();
}

function main() {
  const form = document.getElementById("form") as HTMLFormElement;
  const input = document.getElementById("title") as HTMLInputElement;
  form.addEventListener("submit", (e) => {
    e.preventDefault();
    if (input.value.trim().length === 0) return;
    addTodo(input.value.trim());
    input.value = "";
  });

  loadTodos();
}

document.addEventListener("DOMContentLoaded", main);
<!-- public/index.html -->
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>Bun フルスタック ToDo</title>
  </head>
  <body>
    <h1>Bun.js ToDo アプリ</h1>
    <form id="form">
      <input id="title" placeholder="やることを入力" />
      <button type="submit">追加</button>
    </form>
    <ul id="list"></ul>
    <script src="/bundle.js"></script>
  </body>
</html>
# フロントエンドのバンドル
bun build src/frontend.ts --outfile=public/bundle.js

# サーバーの起動
bun run fullstack/server.ts

このように Bun を使うことで、学習者でも一つのツールチェーンでサーバーサイドとフロントエンドの両方を体験でき、モダンな JavaScript 開発の流れをコンパクトに理解できます。

6
5
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
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?