본문으로 바로가기 본문 바로가기
WIKI 기술 지식 베이스

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 });
		}
	},
};

더 알아보기 (Learn more)