s3mini로 R2 사용하기
제로 의존성·경량 TypeScript S3 클라이언트 s3mini로 R2를 사용하는 예시예요. Node.js, Bun, Cloudflare Workers에서 폴리필 없이 동작하며 버킷 범위 엔드포인트를 사용해 객체를 업로드·조회·삭제하는 방법을 다뤄요.
출처: 문서
본문
s3mini ↗︎는 제로 의존성·경량(약 20 KB minified) TypeScript S3 클라이언트로 AWS SigV4 서명을 사용해요. 폴리필 없이 Node.js, Bun, Cloudflare Workers에서 기본적으로 동작해요.
AWS SDK와 달리 s3mini는 버킷 범위 엔드포인트를 기대해요. 버킷 이름이 엔드포인트 URL의 일부라서 각 작업에 별도의 bucket 파라미터를 전달하지 않아요.
설치 (Install)
npm install s3mini
Node.js / Bun
import { S3mini } from "s3mini";
const s3 = new S3mini({
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
// Bucket-scoped endpoint — include your bucket name in the path
endpoint: `https://${process.env.ACCOUNT_ID}.r2.cloudflarestorage.com/my-bucket`,
region: "auto",
});
// Upload an object
await s3.putObject("hello.txt", "Hello from s3mini!");
// Download an object as a string
const text = await s3.getObject("hello.txt");
console.log(text);
// List objects with a prefix
const objects = await s3.listObjects("/", "hello");
console.log(objects);
// Delete an object
await s3.deleteObject("hello.txt");
Cloudflare Workers
s3mini는 nodejs_compat 호환성 플래그 없이도 Workers에서 기본적으로 동작해요.
import { S3mini } from "s3mini";
interface Env {
R2_ACCESS_KEY_ID: string;
R2_SECRET_ACCESS_KEY: string;
ACCOUNT_ID: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const s3 = new S3mini({
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
endpoint: `https://${env.ACCOUNT_ID}.r2.cloudflarestorage.com/my-bucket`,
region: "auto",
});
const url = new URL(request.url);
const key = url.pathname.slice(1); // strip leading "/"
if (!key) {
return new Response("Missing object key", { status: 400 });
}
switch (request.method) {
case "PUT": {
const data = await request.arrayBuffer();
const contentType =
request.headers.get("content-type") ?? "application/octet-stream";
await s3.putObject(key, new Uint8Array(data), contentType);
return new Response("Created", { status: 201 });
}
case "GET": {
const response = await s3.getObjectResponse(key);
if (!response) {
return new Response("Not Found", { status: 404 });
}
return new Response(response.body, {
headers: {
"content-type":
response.headers.get("content-type") ??
"application/octet-stream",
etag: response.headers.get("etag") ?? "",
},
});
}
case "DELETE": {
await s3.deleteObject(key);
return new Response(null, { status: 204 });
}
default:
return new Response("Method Not Allowed", { status: 405 });
}
},
};