routes プロパティ(静的パス、パラメーター、ワイルドカード用)を使用するか、fetch メソッドで一致しないリクエストを処理することで、Bun.serve() にルートを追加できます。
Bun.serve() のルーターは uWebSocket の ツリーベースのアプローチ をベースに構築されており、SIMD によるアクセラレーションされたルートパラメーターデコード と JavaScriptScript 構造キャッシュ を追加して、現代のハードウェアが許すパフォーマンスの限界を押し上げています。
基本設定
Bun.serve({
routes: {
"/": () => new Response("Home"),
"/api": () => Response.json({ success: true }),
"/users": async () => Response.json({ users: [] }),
},
fetch() {
return new Response("Unmatched route");
},
});Bun.serve() のルートは BunRequest(Request を拡張)を受け取り、Response または Promise<Response> を返します。これにより、HTTP リクエストの送受信に同じコードを使用しやすくなります。
// 簡略化のため
interface BunRequest<T extends string> extends Request {
params: Record<T, string>;
readonly cookies: CookieMap;
}非同期ルート
Async/await
ルートハンドラーで async/await を使用して Promise<Response> を返すことができます。
import { sql, serve } from "bun";
serve({
port: 3001,
routes: {
"/api/version": async () => {
const [version] = await sql`SELECT version()`;
return Response.json(version);
},
},
});Promise
ルートハンドラーから Promise<Response> を返すこともできます。
import { sql, serve } from "bun";
serve({
routes: {
"/api/version": () => {
return new Promise(resolve => {
setTimeout(async () => {
const [version] = await sql`SELECT version()`;
resolve(Response.json(version));
}, 100);
});
},
},
});ルートの優先順位
ルートは特异性の順に一致します。
- 完全一致ルート(
/users/all) - パラメーター付きルート(
/users/:id) - ワイルドカードルート(
/users/*) - グローバルキャッチオール(
/*)
Bun.serve({
routes: {
// 最も具体的なものを最初に
"/api/users/me": () => new Response("Current user"),
"/api/users/:id": req => new Response(`User ${req.params.id}`),
"/api/*": () => new Response("API catch-all"),
"/*": () => new Response("Global catch-all"),
},
});タイプセーフルートパラメーター
文字列リテラルとして渡されると TypeScript はルートパラメーターを解析するため、エディターは request.params へのアクセス時にオートコンプリートを表示します。
import type { BunRequest } from "bun";
Bun.serve({
routes: {
// パラメーターの形状は文字列リテラルとして渡された場合に TypeScript が認識します
"/orgs/:orgId/repos/:repoId": req => {
const { orgId, repoId } = req.params;
return Response.json({ orgId, repoId });
},
"/orgs/:orgId/repos/:repoId/settings": (
// オプション:BunRequest に明示的に型を渡すことができます:
req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">,
) => {
const { orgId, repoId } = req.params;
return Response.json({ orgId, repoId });
},
},
});パーセントエンコードされたルートパラメーター値は自動的にデコードされます。Unicode 文字もサポートされています。無効な Unicode は Unicode 置換文字 &0xFFFD; に置き換えられます。
静的レスポンス
ルートは Response オブジェクト(ハンドラー関数なし)にすることもできます。Bun.serve() はゼロアロケーションディスパッチのために最適化します - ヘルスチェック、リダイレクト、固定コンテンツに最適です。
Bun.serve({
routes: {
// ヘルスチェック
"/health": new Response("OK"),
"/ready": new Response("Ready", {
headers: {
// カスタムヘッダーを渡す
"X-Ready": "1",
},
}),
// リダイレクト
"/blog": Response.redirect("https://bun.com/blog"),
// API レスポンス
"/api/config": Response.json({
version: "1.0.0",
env: "production",
}),
},
});静的レスポンスは初期化後、追加のメモリを割り当てません。一般的に、Response オブジェクトを手動で返すよりも少なくとも 15% のパフォーマンス向上が見込めます。
静的ルートレスポンスはサーバーオブジェクトの存続期間中にキャッシュされます。静的ルートをリロードするには、server.reload(options) を呼び出します。
ファイルレスポンスと静的レスポンス
ルートでファイルを配信する場合、ファイルコンテンツをバッファリングするか直接配信するかによって 2 つの異なる動作があります。
Bun.serve({
routes: {
// 静的ルート - コンテンツは起動時にメモリにバッファリングされます
"/logo.png": new Response(await Bun.file("./logo.png").bytes()),
// ファイルルート - コンテンツは各リクエストでファイルシステムから読み取られます
"/download.zip": new Response(Bun.file("./download.zip")),
},
});静的ルート(new Response(await file.bytes()))は起動時にコンテンツをメモリにバッファリングします。
- リクエスト中のファイルシステム I/O ゼロ - コンテンツは完全にメモリから提供されます
- ETag サポート - キャッシングのために ETag を自動的に生成および検証します
- If-None-Match - クライアントの ETag が一致する場合に
304 Not Modifiedを返します - 404 ハンドリングなし - 欠落したファイルはランタイム 404 ではなく起動時エラーを引き起こします
- メモリ使用量 - ファイルコンテンツ全体が RAM に保存されます
- 推奨: 小さな静的アセット、API レスポンス、頻繁にアクセスされるファイル
ファイルルート(new Response(Bun.file(path)))はリクエストごとにファイルシステムから読み取ります。
- 各リクエストでファイルシステム読み取り - ファイルの存在を確認しコンテンツを読み取ります
- 組み込み 404 ハンドリング - ファイルが存在しないかアクセス不能になった場合に
404 Not Foundを返します - Last-Modified サポート -
If-Modified-Sinceヘッダーのためにファイル変更時間を使用します - If-Modified-Since - ファイルがクライアントのキャッシュバージョンから変更されていない場合に
304 Not Modifiedを返します - Range リクエストサポート -
Content-Rangeヘッダーで部分的なコンテンツリクエストを自動的に処理します - ストリーミング転送 - 効率的なメモリ使用のためにバックプレッシャー処理付きのバッファリーダーを使用します
- メモリ効率的 - ファイル全体ではなく転送中に小さなチャンクのみをバッファリングします
- 推奨: 大きなファイル、動的コンテンツ、ユーザーアップロード、頻繁に変更されるファイル
ファイルのストリーミング
ファイルをストリーミングするには、ボディとして BunFile オブジェクトを含む Response オブジェクトを返します。
Bun.serve({
fetch(req) {
return new Response(Bun.file("./hello.txt"));
},
});Bun.file オブジェクトの slice(start, end) メソッドを使用して、ファイルの一部を送信できます。これにより Response オブジェクトの Content-Range および Content-Length ヘッダーが自動的に設定されます。
Bun.serve({
fetch(req) {
// `Range` ヘッダーを解析
const [start = 0, end = Infinity] = req.headers
.get("Range") // Range: bytes=0-100
.split("=") // ["Range: bytes", "0-100"]
.at(-1) // "0-100"
.split("-") // ["0", "100"]
.map(Number); // [0, 100]
// ファイルのスライスを返す
const bigFile = Bun.file("./big-video.mp4");
return new Response(bigFile.slice(start, end));
},
});fetch リクエストハンドラー
fetch ハンドラーはどのルートにも一致しなかった受信リクエストを処理します。Request オブジェクトを受け取り、Response または Promise<Response> を返します。
Bun.serve({
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/") return new Response("Home page!");
if (url.pathname === "/blog") return new Response("Blog!");
return new Response("404!");
},
});fetch ハンドラーは async/await をサポートしています。
import { sleep, serve } from "bun";
serve({
async fetch(req) {
const start = performance.now();
await sleep(10);
const end = performance.now();
return new Response(`Slept for ${end - start}ms`);
},
});Promise ベースのレスポンスもサポートされています。
Bun.serve({
fetch(req) {
// リクエストを別のサーバーに転送
return fetch("https://example.com");
},
});fetch ハンドラーから Server オブジェクトにアクセスすることもできます。これは fetch 関数に渡される 2 番目の引数です。
// `server` は fetch の 2 番目の引数として渡されます。
const server = Bun.serve({
fetch(req, server) {
const ip = server.requestIP(req);
return new Response(`Your IP is ${ip.address}`);
},
});