この記事は ひとりCloudflareを使い倒す Advent Calendar 2025 の6日目です
※まだまだチュートリアル的な備忘録が続きます
世の中にはアルファベット1文字と数字1文字で構成されるサービスがいろいろあります。
S3, R2, D1, ...(あんまりなかった)
とりあえず、R2 を使ってみます。
R2 って何さ
R2 はオブジェクトストレージです。
画像ファイルとか音楽ファイルとかをオブジェクトを言います。
それを保存できるストレージなので、オブジェクトストレージです。
普通のデータベースは構造化されていますよね。
この記事で言ったら、以下の JSON オブジェクトのように構造化されているといえます。
{
"title": "Cloudflare を使い倒す R2 編",
"author": "toreis-up"
}
どの記事でもこんな感じで扱えますよね。
これが構造化されている、ということです。
(※ラフに「構造化されている」を説明しているので、詳しい定義は調べてください。)
データは 99.999999999% (eleven 9) と、S3 と同じレベルのデータ耐久性を誇り、可用性も SLA で 99.9% と、高い水準です。
例えば、10,000,000 オブジェクトを 1 年で書き込んだとして、1つのオブジェクトがバグってロスするのに 10,000 年かかる計算です。とんでもねえ。
また、S3 互換なので、S3 を使っている製品は R2 に代替できるのではないでしょうか。
ん?R と S 、2 と 3…減って増えてる… (理由は知らない)
R2 を Hono で使ってみる
例によって、Hono で R2 を使ってみます。
今回は、ファイルを投稿するエンドポイントと、ファイルの一覧を出すエンドポイント、そしてファイルをダウンロードするエンドポイントを作ってみます。
セットアップ
pnpm create cloudflare@latest <dir> でプロジェクトを作って、Hono のプロジェクトテンプレートを立ち上げます。
pnpm install でプロジェクトの設定が終わったら、wrangler.jsonc を書き換えます。
{
- "name": "<TBD>",
+ "name": "todo",
"main": "src/index.ts",
- "compatibility_date": "<TBD>",
+ "compatibility_date": "2025-12-06", // 今日の日付
- "assets": {
- "binding": "ASSETS",
- "directory": "./public"
- }
}
pnpm run dev で動くか確認します。
R2 のセットアップ
R2 は、まずバケットを設定する必要があります。
バケットとは:
簡単に言うと、ファイル置き場です。バケットにファイル (オブジェクト) を入れることができます。
さて、そしたら実際にバケットを立てましょう。
pnpm wrangler r2 bucket create hono-bucket
⛅️ wrangler 4.53.0
───────────────────
Creating bucket 'hono-bucket'...
✅ Created bucket 'hono-bucket' with default storage class of Standard.
To access your new R2 Bucket in your Worker, add the following snippet to your configuration file:
{
"r2_buckets": [
{
"bucket_name": "hono-bucket",
"binding": "hono_bucket"
}
]
}
√ Would you like Wrangler to add it on your behalf? ... yes
√ What binding name would you like to use? ... hono_bucket
√ For local dev, do you want to connect to the remote resource instead of a local resource? ... no
そしたら、pnpm run cf-typegen で型定義を自動生成します。
おわり!これでバケットが立ちました。
R2 を Hono から呼ぶ
ファイルをアップロードする
Hono はファイルアップロードに対応しています。
この機能を使って、ファイルが受け取れるように変更します。
import { Hono } from "hono";
const app = new Hono<{ Bindings: CloudflareBindings }>();
app.post("/file", async (c) => {
const body = await c.req.parseBody()
const file = body['file'] // Access the uploaded file here
if (!file || typeof file === 'string') {
return c.text("No file uploaded", 400);
}
const r2 = c.env.hono_bucket;
await r2.put(file.name, file.stream(), {
httpMetadata: {
contentType: file.type,
},
});
return c.text("File uploaded successfully!");
});
export default app;
そしたらサーバーと Postman を起動します。
Postman の Body を form-data に、Key を file にして、Key の右側にある Text となっているプルダウンから File を選択します。
そして Value に好きなファイルを指定して localhost:8787/file に POST してみると…
File uploaded successfully! ということで、ファイルがバケットに打ち上げられました。
ファイル一覧を取得する
バケットのファイル一覧は簡単に取得できます。
await r2.list() で呼んであげるだけです。
// app.post...
+ app.get("/file", async (c) => {
+ const r2 = c.env.hono_bucket;
+ const files = await r2.list();
+
+ const fileNames = files.objects.map((obj) => obj.key);
+
+ const response = `Files in R2 Bucket:\n` + fileNames.join('\n');
+ return c.text(response);
+ })
// export ...
これだけです。
localhost:8787/file を叩いてみますと…
取れました。
ファイルをダウンロードする
ファイルをアップロードできるなら、ダウンロードもできるべきです。
ということで以下のコードを実装します。
// app.get("file", ...
+ app.get("/file/:filename", async (c) => {
+ const filename = c.req.param("filename");
+ const r2 = c.env.hono_bucket;
+
+ const object = await r2.get(filename);
+
+ if (!object) {
+ return c.text("File not found", 404);
+ }
+
+ return c.body(object.body, {
+ headers: {
+ "Content-Type": object.httpMetadata?.contentType || "application/octet-stream",
+ },
+ });
+ });
// export ...
そして、さっきアップロードしたファイル名を file.ext とすると、localhost:8787/file/file.ext にアクセスすると…
返ってきました!:D
Postman ではわかりませんが、音声ファイルなども返ってきます。ブラウザで確認してみるとよいでしょう。
おわりに
またしてもローカロリーになってしまいました…
S3 の簡単な使いかたを自分が理解するだけの記事ですが、ちょっとだけ入門してみたい誰かのためになれば幸いです。
R2 と Cloudflare Images を組み合わせるとなにかが出来上がる気もしますよね。(ネタバレ)
いつか挑戦してみたいです。


