Open3
Remix のアプリからユーザーが送信した画像を Cloudflare R2 にアップロードして Cloudflare D1 に保存する
ピン留めされたアイテム
前提
- Remix + Cloudflare Workers + Cloudflare D1 環境構築 が終わっている
- コード上でクエリを叩いて DB に任意のデータが保存できる状態になっている
目的
- input の file フィールドから送信された画像を R2 にアップロードし、その URL を DB` に保存する
export async function action({ context, request }: ActionFunctionArgs) {
try {
const formData = await request.formData();
const { title, text, imageFile, author, errors } =
validateFormData(formData);
if (Object.keys(errors).length > 0) {
return json({ title, text, imageFile, author, errors });
}
// TODO: ここの関数を作成する
const imageUrl = await uploadImageFile(imageFile);
const database = context.cloudflare.env.DB;
const query = `INSERT INTO Recipes (title, text, imageUrl, author) VALUES ('${title}', '${text}', '${imageUrl}', '${author}')`;
await database.prepare(query).run();
} catch (e) {
return redirect("/error");
}
return redirect("/recipes");
}
Cloudflare R2 に画像をアップロードするための準備
1. バケットを作成
- cloudflare のダッシュボードの R2 → 概要 → バケット作成
2. APIトークンを作成
-
cloudflare のダッシュボードの R2 → 概要 → R2 API トークンの管理 → APIトークンを作成
-
以下のリクエストで取得したトークンがアクティブ状態になっているか確認できる
curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer 取得したトークンをコピペ"
3. アプリとバケットを連携
-
wrangler.toml
に以下のコードを追加
[[r2_buckets]]
binding = "R2" # アプリ内で使用する変数名なので何でもOK
bucket_name = "image" # 作成したバケット名
4. エンドポイントを作成
- worker(server.ts)に以下のコードを追加
interface Env {
DB: D1Database;
R2: R2Bucket;
}
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
const key = url.pathname.slice(1); // URLからファイル名を取得
if (request.method === "PUT") {
await env.R2.put(key, request.body); // R2バケットにファイルを保存
return new Response(`Put ${key} successfully!`, { status: 200 });
}
return new Response("Method not allowed", { status: 405 }); // PUT以外のリクエストを拒否
},
};
- worker をデプロイ
npx wrangler deploy
- 以下のリクエストが成功すれば R2 のダッシュボードからアップロードされた画像を確認できる
curl -X PUT "https://{xxx}.dev/images/IMG_0275.png" \
--upload-file /Users/ktgwshota/Downloads/IMG_0275.png
目的の関数を作る
async function uploadImageFile(imageFile: File): Promise<string> {
const uuid = v4();
const createPath = `images/${uuid}.${imageFile.name}`;
const response = await fetch(
`https://{xxx}.dev/${createPath}`,
{
method: "PUT",
body: imageFile,
}
);
if (!response.ok) {
throw new Error("画像のアップロードに失敗しました");
}
// FIXME: ローカル用のURLなので、本番で運用する場合は考える必要がある
// ref: https://developers.cloudflare.com/r2/buckets/public-buckets/#enable-managed-public-access
const imageUrl = `https://{xxx}.r2.dev/${createPath}`;
return imageUrl;
}