Bunアプリケーションをデプロイするには?

How to Deploy an Bun Application_
How to Deploy an Bun Application_

Bunは、高速、軽量、使いやすさを追求したJavaScriptランタイムです。Zigで書かれ、Safariを動かすJavaScriptエンジンであるJavaScriptCoreによって動きます。

Bunには、Node.js互換のパッケージ・マネージャー、テスト・ランナー、Bunドラーが組み込まれている。また、HTTPサーバーの起動やファイルの書き込みといった一般的なタスクを実行するための、高度に最適化された最小限のAPIセットも提供する。

この記事では、Bunを使ってシンプルなWeb APIを構築し、Back4appコンテナを使ってBack4app上にデプロイします。Bunアプリケーションをホストする方法については、このまま読み進めてください。

Bunの利点

Bunの最初の発表以来、2023年9月のV1リリース前にもかかわらず、BunはJavaScriptコミュニティでますます人気が高まっている。その理由をいくつか紹介しよう。

スピード

Bunは、パフォーマンス、安全性、可読性を重視したシステム・プログラミング用に設計された低レベル・プログラミング言語Zigで書かれている。CやC++に代わる現代的なプログラミング言語として開発された。

さらに、ChromeのV8 JavaScriptエンジンを使うNode.jsやDenoとは異なり、Safariを動かすJavaScriptCoreエンジンを使う。

ZigとJavaScript Coreに加えて、BunはJavaScript用に最適化されたカスタムメモリアロケータや、実行時にコードを最適化するジャストインタイム(JIT)コンパイラなど、他にも多くの技術を使用している。

全体として、Zig、JavaScript Core、その他の最適化の組み合わせにより、Bunは他のランタイムと比較して非常に高速なJavaScriptランタイムとなっている。

Node.jsとの互換性

Bunは、Node.jsをそのまま置き換えるように設計されているため、Node.jsのすべてのAPIと互換性がある。

また、crypto、fs、pathなどの組み込みNode.jsモジュールもすべて備えています。利用可能なNode.jsモジュールと利用不可能なNode.jsモジュールは、Bun.jsのドキュメントで確認できます。

さらに、Bunはnpm互換のパッケージマネージャです。つまり、Bunを使ってNode.jsのパッケージをインストール・管理することができます。

アウトオブボックスでTypeScriptをサポート

BunはTypeScriptをネイティブかつシームレスにサポートしているため、プロジェクトでTypeScriptを使用したい、または使用する必要がある場合に最適な選択肢となる。

TypeScriptはJavaScriptの拡張版であり静的型付けバージョンであり、JavaScriptの開発を強化するために高度な言語機能と静的型付けを導入している。

Bunを使えば、追加の設定は必要なく、TypeScriptの機能を有効にするための余分なセットアップやビルド手順も必要ない。

Bunの限界

その長所とは裏腹に、Bunにはプロジェクトで使用する前に考慮しなければならない制限がある。

限られたリソース

Bunはまだ比較的新しく、コミュニティも小さい。Bun-js固有の開発をカバーするリソースは多くないので、ランタイムの使い方を理解するのに苦労するかもしれない。

しかし、Bunのドキュメントは包括的で、貴重な参照ポイントとして役立つ。問題が発生した場合は、Discordチャンネルでサポートを求めることもできる。

Windows対応

Bunは現在、Windowsオペレーティング・システムを限定的にサポートしている。本稿執筆時点では、Windowsではランタイムのみがサポートされている。

テストランナー、パッケージマネージャー、Bunドラーはまだ開発中であり、Windowsでは動作しません。Bunアプリをホストする方法については、このまま読み進めてください。

Bunアプリを作る

Bunを使うには、インストールする必要がある。

macOS、WSL、LinuxにBunをインストールするには、以下のコマンドを実行する:

curl -fsSL https://bun.sh/install | bash

開発環境のセットアップ

新しいBunプロジェクトを作成するには、以下のコマンドを実行する:

bun init

上記のコマンドを実行すると、プロジェクト・ディレクトリに空のBunプロジェクトが初期化されます。

チュートリアルでは、最速のBun HTTPサーバーフレームワークの1つあるElysiaを使ってシンプルなAPIを構築する。

以下のコマンドを実行して、このプロジェクトに必要なElysiaとその他の依存関係をインストールする:

bun add elysia knex dotenv pg

コマンドでインストールされる他の依存関係は以下の通り:

  • Knex, クエリビルダ。この依存関係を使用して、データベースとのやり取りを簡単にします。
  • dotenvは、プロジェクトの環境変数の管理を手助けするパッケージです。
  • pg(Postgresデータベースドライバ)は、postgresデータベースと対話するためのものです。

次に、プロジェクトでknexを実行して初期化する:

knex init

上記のコマンドはknexfile.jsファイルを作成します。このファイルにはデータベースの設定オプションが含まれています。

ファイル内のコードを以下のコードブロックに置き換える:

require("dotenv").config();

export const development = {
  client: "pg",
  connection: process.env.DATABASE_URL,
  migrations: {
    directory: "./db/migrations",
  }
};

次に、プロジェクトのルート・ディレクトリにdb.jsファイルを作成し、以下のコード・ブロックを追加する。

const knex = require("knex");
const knexFile = require("./knexfile.js");

const environment = process.env.NODE_ENV || "development";

export default knex(knexFile[environment]);

次に、.envファイルを作成し、データベース接続の詳細またはURIをファイルに追加する。

例えば、こうだ:

DATABASE_URL = <YOUR_DATABASE_URI>

YOUR_DATABASE_URI “をあなたのデータベースのURLに置き換えてください。

注:個人情報をバージョン管理にコミットしないように、.envファイルを.gitignoreファイルに追加してください。

データベースモデルの作成

Knexを使用してデータベースモデルを作成するには、マイグレーションファイルを作成し、Knexを使用してSQLコマンドを作成します。

以下のコマンドを実行して、最初のマイグレーションを作成する:

knex migrate:make blog

次に、生成されたマイグレーションファイルのコードを以下のコードブロックに置き換える:

/**
 * @param { import("knex").Knex } knex
 * @returns { Promise<void> }
 */
export function up (knex) {
  return knex.schema.createTable("blog", (table) => {
    table.increments("id").primary();
    table.string("title").notNullable();
    table.string("content").notNullable();
    table.string("author").notNullable();
    table.timestamp("created_at").defaultTo(knex.fn.now());
  });
}

/**
 * @param { import("knex").Knex } knex
 * @returns { Promise<void> }
 */
export function down (knex) {
  return knex.schema.dropTable("blog");
}

最後に、以下のコードブロックを実行してマイグレーションファイルを実行します:

knex migrate:latest

上記のコマンドは、先に生成したマイグレーションファイルを実行し、データベースにBlogテーブルを作成します。

Bun-Elysia・サーバーの作成

このステップでは、単純なAPIサーバーを作成する。

index.tsファイルを開き、以下のコードブロックを追加する:

//index.ts
const { Elysia } = require("elysia");
let db = require("./db");

db = db.default;

const app = new Elysia();

以下のコードブロックでは、Elysiaをインポートし、Elysiaフレームワークのインスタンス(app)を作成しています。

ルートハンドラの作成

次に、アプリケーションのルートハンドラを作成します。作成するハンドラは、以下のルート用になります:

  • POST /posts/create-new-post
  • GET /posts
  • GET /posts/:id
  • PATCH /posts/:id/update-post
  • DELETE /posts/:id/delete-post

以下のコードブロックをindex.tsファイルに追加し、”/posts/create-new-post “のハンドラを作成する:

app.post("/posts/create-new-post", async (context) => {
  try {
    //Extract the title, content and author from the request body
    const { title, content, author } = context.body;

    //Insert the post into the database
    const post = await db("blog").insert({
      title,
      content,
      author,
    });

    //Send response to the client
    return new Response(JSON.stringify(post));
  } catch (error: any) {
    //Send error to the client
    return new Response(error.message, { status: 500 });
  }
});

上のコードブロックは、新しい投稿をデータベースに追加するエンドポイントのルートハンドラです。

以下のコードブロックをindex.tsファイルに追加し、すべての投稿を取得するハンドラ”/posts “を作成します:

app.get("/posts", async (context) => {
  try {
    //Get all posts from the database
    const posts = await db.select("*").from("blog");

    //Send response to the client
    return new Response(JSON.stringify(posts));
  } catch (error: any) {
    //Send error to the client
    return new Response(error.message, { status: 500 });
  }
});

以下のコードブロックをindex.tsファイルに追加し、id “/posts/:id “によって単一の投稿をフェッチするハンドラを作成する:

app.get("/posts/:id", async (context) => {
  //Extract the id from the request params
  const { id } = context.params;

  //Get the post from the database
  const post = await db("blog").where({ id });

  //If the post is not found, send a 404 response
  if (post.length === 0) {
    return new Response("Post not found", { status: 404 });
  }

  //Send response to the client
  return new Response(JSON.stringify(post));
});

以下のコードブロックをindex.tsファイルに追加して、id “/posts/:id/update-post “によるペイロードのデータで単一の投稿を更新するハンドラを作成します:

app.patch("/posts/:id/update-post", async (context) => {
  //Extract the id from the request params
  const { id } = context.params;

  //Extract the title and content from the request body
  const { title, content } = context.body;

  //Update the post in the database
  const post = await db("blog").where({ id }).update(
    {
      title,
      content,
    },
    ["id", "title", "content"]
  );

  //Send response to the client
  return new Response(JSON.stringify(post));
});

以下のコードブロックをindex.tsファイルに追加し、id “/posts/:id/delete-post “によって単一の投稿を削除するハンドラを作成します:

app.delete("/posts/:id/delete-post", async (context) => {
  //Extract the id from the request params
  const { id } = context.params;

  //Delete the post from the database
  const post = await db("blog").where({ id }).del();

  //Send response to the client
  return new Response(JSON.stringify(post));
});

最後に、以下のコードブロックを追加して、アプリケーションのPORTを設定します。

app.listen(3000, () => {
  console.log("Server running on port 3000");
});

以下のコマンドを実行してアプリを起動する:

bun --watch index.ts

BunアプリをBack4appコンテナにデプロイする

Bunアプリのデプロイにはいくつかのステップが必要です。

ステップ1:Dockerfileを書く

Dockerfileを作成するには、ターミナルで以下のコマンドを実行する。

touch Dockerfile

上記のコマンドを実行すると、プロジェクトのルート・ディレクトリにDockerfileが作成される。

次に、Dockerfileを開き、以下のコード・ブロックを追加する:

FROM oven/bun

WORKDIR /app

COPY package.json .
COPY bun.lockb .

RUN bun install

COPY . .

EXPOSE 3000

CMD ["bun", "index.ts"]

上記のDockerfileでは、1行目のFROM oven/bunで使用するベースイメージを指定しています。このイメージは、Bunランタイムとその依存関係をすべて含むビルド済みイメージである。

次の行、WORKDIR /appは、イメージの作業ディレクトリを設定します。これはアプリケーション・コードがコピーされ、実行されるディレクトリです。

次の2行、COPY package.json .COPY bun.lockb .は、package.jsonファイルとbun.lockbファイルをカレントディレクトリからイメージにコピーします。これらのファイルは、Bunランタイムがアプリケーションの依存関係をインストールするために必要です。

次の行、RUN bun installは、Bunランタイムを使用してアプリケーションの依存関係をインストールする。

次の行、COPY . . は、カレント・ディレクトリー全体をイメージにコピーします。これにはアプリケーション・コードとその他の必要なファイルが含まれます。

次の行「EXPOSE 3000」は、コンテナから外部にポート3000を公開している。これは、アプリケーションがリッスンするポートである。

最後の行、CMD ["bun", "index.ts"]は、コンテナの起動時に実行されるコマンドを指定します。このコマンドはBunランタイムを起動し、アプリケーションのindex.tsファイルを実行します。

最後に、コードを GitHubに プッシュする

ステップ2:Back4appアプリケーションの作成

Bunアプリケーションをホストするための次のステップは、Back4Appで新しいアプリケーションを作成することです。まず、Back4Appアカウントにサインインするか、アカウントをお持ちでない場合はサインアップしてください。ログインすると、Back4Appのダッシュボードが表示されます。

NEW APP “ボタンをクリックし、“Containers as a Service“オプションを選択する。

Back4app BaaSとCaaSの比較

Bunアプリをホストするための次のステップとして、GitHubアカウントをBack4appアカウントに接続します。アカウントを接続することで、Back4appはあなたのアカウント上のリポジトリにアクセスできるようになります。

アカウント内のすべてのリポジトリ、または特定のリポジトリへのアクセスを許可することができます。デプロイするアプリケーション(この場合は、このチュートリアルでビルドしたアプリケーション)を選択し、Select をクリックします。

新しいアプリ back4app

Selectボタンをクリックすると、設定ページが表示され、PORTや環境変数など、アプリの詳細を入力します。

詳細を入力したら、[Create App]ボタンをクリックします。これでデプロイプロセスが開始されます。デプロイは成功し、アプリにアクセスするためのURLが発行されるはずですが、もし失敗した場合は、Back4app ChatGPTインテグレーションを利用することで、Dockerファイルで発生した問題を解決することができます。

また、Back4appの詳細なログとトラブルシューティングガイドを使用して、デプロイメントエラーを手動でトラブルシューティングすることもできます。

結論

この記事では、Bun JavaScriptランタイムについて、その利点と明らかな制限について説明した。また、Elysia、Knex、PostgreSQLを使用してBunアプリを構築する方法についても説明しました。

最後に、Back4appコンテナと、Bunアプリケーションをプラットフォーム上にデプロイする方法について説明した。

Bunを使用する際には、まだ初期段階であり、将来的に大きな変更が加えられる可能性があることに注意することが重要である。

よくあるご質問

Bunとは何ですか?

Bun は、高速かつ効率的に設計された JavaScript ランタイムです。JavaScriptCore エンジン上に構築されており、さまざまな最適化 (低レベル言語である Zig を使用する利点) を利用して高速化を実現しています。

Bun アプリをデプロイするには?

– Bun アプリを作成します。
– Dockerfile を作成します。
– Bun アプリケーションを GitHub にプッシュします。
– Back4app アカウントを開くか、既存のアカウントにサインインします。
– Back4app で新しい「CaaS」アプリを作成します。
– デプロイするアプリケーションへのアクセス権を Back4app に付与します。
– アプリを選択し、構成の詳細を入力します。
– [デプロイ] をクリックします。


Leave a reply

Your email address will not be published.