43
42

Expressより18倍速いというElysiaJS、Bun圧倒的?

Last updated at Posted at 2023-11-28

Node.js の Webフレームワークと言えば Express ですが、それより18倍速い、という Bun の Web フレームワーク Elysia(エリシア) を見ていこうかなと思う今日この頃です。

さて今回は、Elysia の Quick Start 周辺を、少し脱線しつつうんちくも差し込みながら眺めようと思います。もし説明が回りくどいと思われる方は、先に本家の Quick Start ( https://elysiajs.com/quick-start.html ) へどうぞ^_^;

image.png

それにしても、Express の18倍とは凄いですね。まぁ、ベンチは実際とは違うものですけど、このチャートを見ると、上位の3つが圧倒的です。そしてどれも Bun なのですよね。感慨深いです。

関連サイト

では、まずとりあえず、主な関連サイトを並べておきますね。ElysiaJS 本家と Github、そして Bunのドキュメントです。

今回の環境

クラウド: Azure VM (これはオンプレでも何でも良い)
OS: Ubuntu 20.04.6 LTS (GNU/Linux 5.15.0-1050-azure x86_64)
Bun: v1.0.14 ( https://bun.sh/blog )
Node.js: v20.9.0

もちろん、ようは JavaScript や TypeScriptですから、これでなければいけないという事はありません。

Quick Start

何はともあれクイックスタートです。ほんとに動くのか?というところから始めてみます。

本家の Quick Start ( https://elysiajs.com/quick-start.html ) にあるコードは下記コードのタイトルに「」をつけておきます。ついていないのはこの記事の補足になります。

ディレクトリを作っておく
$ mkdir elysia
$ cd elysia

まずは elysia (好きな名前で良いです) というディレクトリを用意してそこへ入ります。

次は、本家の Quick Start にある最初の呪文です。Bunのインストールですね。

Bunのインストール

◎  Bunのインストール
$ curl https://bun.sh/install | bash

これで Bun が入るのですが Bun のサイトでよくみかける "curl -fsSL https://bun.sh/install | bash" これと少し違いますね。まぁ、ほぼ余談ですがオプションパラメータが違うだけです。

寄り道して説明しておきます。

curl オプションの説明
curl: HTTPリクエストを行うためのコマンドラインツール。
-fsSL: curlと共に使用されるオプションの組み合わせです。

詳細
-fまたは--fail:HTTPエラーを失敗として扱います。つまり、HTMLエラーページを表示しません。
-sまたは--silent:サイレントモード。進行状況やエラーメッセージを表示しません。
-Sまたは--show-error:エラーメッセージを表示します。これは、-sと組み合わせる場合に必要です。
-Lまたは--location:リダイレクトに従います。
したがって、-fsSLは、サイレントなHTTPリクエストを行い、リダイレクトに従い、エラーを失敗として扱うことを意味します。

https://bun.sh/installは、BunをダウンロードするためのURL。

|:パイプ。左側のコマンド(この場合はcurl)の出力を
右側のコマンド(この場合はbash)の入力として使用します。

bash:Bashシェル。URLから受け取ったスクリプトやコンテンツを解釈して実行するために使用されます。

要するに、このコマンドは指定されたURL(https://bun.sh/install)から
Bun をダウンロードし、それをBashシェル(bash)で実行するものです。

上記オプション無しで実行するとこんなアニメが表示され Bun が ダウンロード/ 実行 されます。

image.png

新しい elysia プロジェクトを作る

さて、次は、bun create を使用して新しいプロジェクトを立ち上げます。この「 bun create elysia hi-elysia」のhi-elysiaの部分はプロジェクト名で自由に命名します。

bun criate (https://bun.sh/docs/cli/bun-create) は、npm パッケージ、GitHub リポジトリ、またはローカル テンプレートを使用して新しいプロジェクトを作成しますが、詳しくは bun criate のリンク先をご覧ください。

◎ 新しいプロジェクトを作る
$ bun create elysia hi-elysia

ディレクトリツリーはこう生成されました

この結果ディレクトリはこうなります。

生成されたディレクトリ
./hi-elysia/
    │  ├─README.md
    │  ├─bun.lockb
    │  └─node_modules
    │      ├─@sinclair
    │      ├─bun-types
    │      ├─cookie
    │      ├─elysia
    │      ├─eventemitter3
    │      ├─fast-decode-uri-component
    │      ├─fast-querystring
    │      ├─memoirist
    │      └─openapi-types
    ├─package.json
    ├─src
    │  └─index.ts
    └─tsconfig.json

同時に Bun のモジュールキャッシュもできていた

因みにこの時、Bunは、下記のようにキャッシュディレクトリ「~/.bun/install/cache」配下へ次のようなキャッシュをダウンロードして、ディスク全体でパッケージモジュールを一意に管理します。

この場合例えば、 openapi-types@12.1.3 は通常の node_modules に入ってるのと同様のモジュールファイルです。

openapi-types は openapi-types@12.1.3 へのハードリンク。hogehoge.npmはその設定データになります。

ハードリンクやソフトリンクは、元のファイルと同じデータを共有するため、実データの複製は行われません。

そのため、通常のファイルと同じデータを持つハードリンクやソフトリンクを大量に作成しても、データ量は元のファイルとほぼ同じで、更に重複しない一意のキャッシュなのでディスクを節約します。

キャッシュディレクトリに保存されたフォイルとリンク
$ ls -l ~/.bun/install/cache
total 420
-rw-rw-r-- 1 tato tato   8488 Nov 27 21:59 05ed5331e4a4d393.npm
-rw-rw-r-- 1 tato tato   7336 Nov 27 21:59 13963cdf6b29d8e5.npm
-rw-rw-r-- 1 tato tato   1136 Nov 27 21:59 14a2c3ccac884310.npm
-rw-rw-r-- 1 tato tato 112144 Nov 27 21:59 22eb1288cd67ec7b.npm
-rw-rw-r-- 1 tato tato   2048 Nov 27 21:59 396e992ab5031124.npm
-rw-rw-r-- 1 tato tato  12232 Nov 27 21:59 4728612665ed89a7.npm
-rw-rw-r-- 1 tato tato  12576 Nov 27 21:59 77608e31e39bdf0f.npm
-rw-rw-r-- 1 tato tato  98576 Nov 27 21:59 85d93ddef8e3a043.npm
drwxr-xr-x 4 tato tato   4096 Nov 27 21:59 @sinclair
drwxr-xr-x 2 tato tato   4096 Nov 27 21:59 bun-types
drwxr-xr-x 2 tato tato   4096 Nov 27 21:59 bun-types@1.0.14
drwxr-xr-x 2 tato tato   4096 Nov 27 21:59 cookie
drwxr-xr-x 2 tato tato   4096 Nov 27 21:59 cookie@0.6.0
-rw-rw-r-- 1 tato tato  84048 Nov 27 21:59 e7b41234e0029d1f.npm
drwxr-xr-x 2 tato tato   4096 Nov 27 21:59 elysia
drwxr-xr-x 3 tato tato   4096 Nov 27 21:59 elysia@0.7.29
drwxr-xr-x 2 tato tato   4096 Nov 27 21:59 eventemitter3
drwxr-xr-x 3 tato tato   4096 Nov 27 21:59 eventemitter3@5.0.1
drwxr-xr-x 2 tato tato   4096 Nov 27 21:59 fast-decode-uri-component
drwxr-xr-x 2 tato tato   4096 Nov 27 21:59 fast-decode-uri-component@1.0.1
drwxr-xr-x 2 tato tato   4096 Nov 27 21:59 fast-querystring
drwxr-xr-x 3 tato tato   4096 Nov 27 21:59 fast-querystring@1.1.2
drwxr-xr-x 2 tato tato   4096 Nov 27 21:59 memoirist
drwxr-xr-x 4 tato tato   4096 Nov 27 21:59 memoirist@0.1.4
drwxr-xr-x 2 tato tato   4096 Nov 27 21:59 openapi-types
drwxr-xr-x 3 tato tato   4096 Nov 27 21:59 openapi-types@12.1.3

さて、これは何を意味するでしょう?Bun は Node.js/npm と互換性があるので、node_modules がディレクトリ内や上位ディレクトリにあれば、それを参照しますが、もしなければ、この ~/.bun/install/cache のキャッシュを参照します。

つまり、下記の作業などで node_modules を削除することで Bun スタイルの仕組みに切り替えて、Bun の恩恵を受けることができます。

この辺、くわしくは先日ふれた Bun のオンザフライインストールとキャッシュの記事あたりでもご覧ください。

node_modules がそのディレクトリに無くても動作するのです。

node_modules を削除する
$ rm -rf node_modules

では、生成された hi-elysia ディレクトリに入ってみましょう。

◎ hi-elysia ディレクトリに入る
$ cd hi-elysia

package.json はこう生成されてる

まず、package.json がどうなってるのか見ておきます。

package.jsonを開く
$ cat package.json
package.jsonの中身
{
  "name": "hi-elysia",
  "version": "1.0.50",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "bun run --watch src/index.ts"
  },
  "dependencies": {
    "elysia": "latest"
  },
  "devDependencies": {
    "bun-types": "latest"
  },
  "module": "src/index.js"
}t

"scripts" に "dev": "bun run --watch src/index.ts" とあるので、普通の package.json 同様に「bun dev」でその bun run...を開発用として実行します。

--watch は監視モードで bun を実行するためのオプションで、ファイルの変更が検出されるたびに、実行中の Bun プロセスが再起動されるので、コード修正の度に立ち上げなおす手間を省けます。

スクリプト本体をみてみる

では、src/index.ts を開いてみましょう。

package.json の「bun dev」で実行されるファイルですね。

src/index.ts を開く
$ cat src/index.ts
◎ src/index.ts
import { Elysia } from "elysia";

const app = new Elysia().get("/", () => "Hello Elysia").listen(9000);

console.log(
  `?  Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);

src/index.ts の中をみればわかる通り、これは 2行 で書いた簡単なHTTPサーバーですね。

import { Elysia } from "elysia" で Elysia を読み込んで、

new Elysia() で作成したインスタンス app に HTTPメソッド get を呼び出して "/" パスにアクセスしたら "Hello Elysia" を返す処理を port 9000 に立てています。
それでは実行してみましょう。

実行する

実行する
$ bun dev

あるいは、下記などで、

実行する
$ bun src/index.ts

このサーバーを起動してから、 http://localhost:9000http://アドレス:9000 へブラウザなどでアクセスするとこんなレスポンスが返ります。

image.png

※もちろん、この時、ネットワーク設定やファイアウォールなどで port 9000 が開いてることは必要です。例えば、Azure や AWSなどでは、ネットワーク設定のインバウンド(受信用)ポートで 9000 を開けておきます。

上記のコードは例えばこう書いても同じです

サンプル
import { Elysia } from "elysia";

new Elysia()
      .get("/", () => {
        return "Hello Elysia"
      })
      .listen(9000);

そして、jQuery などのようにメソッドチェーンをつないでいくこともできます。

サンプル
new Elysia()
    .get('/hi', () => 'Hi')
    .post('/hi', () => 'From Post')
    .put('/hi', () => 'From Put')
    .route('M-SEARCH', () => 'Custom Method')
    .listen(8080)

Elysiaの簡単なサンプル群

ElysiaJS>Cheat Sheet(チートシート)にシンプルなサンプル群が並んでいますので眺めてみることをお勧めします。

tsconfig.json

上記作業で生成された TypeScript プロジェクトの設定ファイル tsconfig.json の有効なプロパティは以下の通りでした。

下記の「cat tsconfig.json | grep -v "//"」は、 tsconfig.json ファイルから "//" でコメントアウトされていない行、つまり有効な設定を出力してくれます。

tsconfig.json 内の有効なプロパティ
$ cat tsconfig.json | grep -v "//"

{
  "compilerOptions": {

    /* Projects */

    /* Language and Environment */
    "target": "ES2021",                                  
        /* Set the JavaScript language version 
            for emitted JavaScript and include compatible library declarations. */

    /* Modules */
    "module": "ES2022",                                
        /* Specify what module code is generated. */
    "moduleResolution": "node",                      
        /* Specify how TypeScript looks up a file from a given module specifier. */
    "types": ["bun-types"],                                      
        /* Specify type package names to be included 
            without being referenced in a source file. */

    /* JavaScript Support */

    /* Emit */

    /* Interop Constraints */
    "esModuleInterop": true,                             
        /* Emit additional JavaScript to ease support 
            for importing CommonJS modules.
            This enables 'allowSyntheticDefaultImports' for type compatibility. */
    "forceConsistentCasingInFileNames": true,            
        /* Ensure that casing is correct in imports. */

    /* Type Checking */
    "strict": true,                                      
        /* Enable all strict type-checking options. */

    /* Completeness */
    "skipLibCheck": true                                 
        /* Skip type checking all .d.ts files. */
  }
}

今日は、ElysiaJS の HTTPサーバーを眺めてみましたが、Elysiaには 高速なuWS (uWebSockets( https://github.com/uNetworking/uWebSockets ) )を利用した WebSocket サーバーも簡単に作れますし、ファイルアップロードなどなど様々な機能もあります。

順次、試していきたいと思っています。

最近 Qiita に書いた Bun 関連の記事

43
42
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
43
42