生產伺服器通常讀取、上傳和寫入檔案至相容 S3 的物件儲存服務,而不是本機檔案系統。 過去,這意味著您在開發中使用的本機檔案系統 API 無法在生產環境中使用。 當您使用 Bun 時,情況就不同了。
Bun 的 S3 API 速度很快

Bun 提供快速的原生綁定,用於與相容 S3 的物件儲存服務互動。 Bun 的 S3 API 設計簡潔,感覺與 fetch 的 Response
和 Blob
API 相似(如同 Bun 的本機檔案系統 API)。
import { s3, write, S3Client } from "bun";
// Bun.s3 reads environment variables for credentials
// file() returns a lazy reference to a file on S3
const metadata = s3.file("123.json");
// Download from S3 as JSON
const data = await metadata.json();
// Upload to S3
await write(metadata, JSON.stringify({ name: "John", age: 30 }));
// Presign a URL (synchronous - no network request needed)
const url = metadata.presign({
acl: "public-read",
expiresIn: 60 * 60 * 24, // 1 day
});
// Delete the file
await metadata.delete();
S3 是 事實上的標準 網路檔案系統。 Bun 的 S3 API 適用於相容 S3 的儲存服務,例如
- AWS S3
- Cloudflare R2
- DigitalOcean Spaces
- MinIO
- Backblaze B2
- ...以及任何其他相容 S3 的儲存服務
基本用法
有幾種方式可以與 Bun 的 S3 API 互動。
Bun.S3Client
& Bun.s3
Bun.s3
等同於 new Bun.S3Client()
,依賴環境變數取得憑證。
若要明確設定憑證,請將其傳遞至 Bun.S3Client
建構函式。
import { S3Client } from "bun";
const client = new S3Client({
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// sessionToken: "..."
// acl: "public-read",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
// endpoint: "https://<region>.digitaloceanspaces.com", // DigitalOcean Spaces
// endpoint: "https://127.0.0.1:9000", // MinIO
});
// Bun.s3 is a global singleton that is equivalent to `new Bun.S3Client()`
使用 S3 檔案
S3Client
中的 file
方法會傳回 S3 上檔案的延遲參考。
// A lazy reference to a file on S3
const s3file: S3File = client.file("123.json");
如同 Bun.file(path)
,S3Client
的 file
方法是同步的。 在您呼叫依賴網路請求的方法之前,它不會發出任何網路請求。
從 S3 讀取檔案
如果您使用過 fetch
API,您會熟悉 Response
和 Blob
API。 S3File
擴展了 Blob
。 適用於 Blob
的相同方法也適用於 S3File
。
// Read an S3File as text
const text = await s3file.text();
// Read an S3File as JSON
const json = await s3file.json();
// Read an S3File as an ArrayBuffer
const buffer = await s3file.arrayBuffer();
// Get only the first 1024 bytes
const partial = await s3file.slice(0, 1024).text();
// Stream the file
const stream = s3file.stream();
for await (const chunk of stream) {
console.log(chunk);
}
記憶體最佳化
text()
、json()
、bytes()
或 arrayBuffer()
等方法會盡可能避免在記憶體中複製字串或位元組。
如果文字碰巧是 ASCII,Bun 會直接將字串傳輸到 JavaScriptCore(引擎),而無需轉碼且無需在記憶體中複製字串。 當您使用 .bytes()
或 .arrayBuffer()
時,它也會避免在記憶體中複製位元組。
這些輔助方法不僅簡化了 API,也使其速度更快。
寫入 & 上傳檔案至 S3
寫入 S3 同樣簡單。
// Write a string (replacing the file)
await s3file.write("Hello World!");
// Write a Buffer (replacing the file)
await s3file.write(Buffer.from("Hello World!"));
// Write a Response (replacing the file)
await s3file.write(new Response("Hello World!"));
// Write with content type
await s3file.write(JSON.stringify({ name: "John", age: 30 }), {
type: "application/json",
});
// Write using a writer (streaming)
const writer = s3file.writer({ type: "application/json" });
writer.write("Hello");
writer.write(" World!");
await writer.end();
// Write using Bun.write
await Bun.write(s3file, "Hello World!");
處理大型檔案 (串流)
Bun 會自動處理大型檔案的多部分上傳,並提供串流功能。 適用於本機檔案的相同 API 也適用於 S3 檔案。
// Write a large file
const bigFile = Buffer.alloc(10 * 1024 * 1024); // 10MB
const writer = s3file.writer({
// Automatically retry on network errors up to 3 times
retry: 3,
// Queue up to 10 requests at a time
queueSize: 10,
// Upload in 5 MB chunks
partSize: 5 * 1024 * 1024,
});
for (let i = 0; i < 10; i++) {
await writer.write(bigFile);
}
await writer.end();
預先簽署 URL
當您的生產服務需要讓使用者上傳檔案到您的伺服器時,使用者直接上傳到 S3 通常比您的伺服器充當中間人更可靠。
為了方便起見,您可以為 S3 檔案預先簽署 URL。 這會產生一個帶有簽章的 URL,允許使用者安全地將該特定檔案上傳到 S3,而無需洩露您的憑證或授予他們不必要的存取您儲存桶的權限。
預設行為是產生一個 GET
URL,在 24 小時後過期。 Bun 會嘗試從檔案副檔名推斷內容類型。 如果無法推斷,則預設為 application/octet-stream
。
import { s3 } from "bun";
// Generate a presigned URL that expires in 24 hours (default)
const download = s3.presign("my-file.txt"); // GET, text/plain, expires in 24 hours
const upload = s3.presign("my-file", {
expiresIn: 3600, // 1 hour
method: "PUT",
type: "application/json", // No extension for inferring, so we can specify the content type to be JSON
});
// You can call .presign() if on a file reference, but avoid doing so
// unless you already have a reference (to avoid memory usage).
const myFile = s3.file("my-file.txt");
const presignedFile = myFile.presign({
expiresIn: 3600, // 1 hour
});
設定 ACL
若要設定預先簽署 URL 的 ACL(存取控制列表),請傳遞 acl
選項
const url = s3file.presign({
acl: "public-read",
expiresIn: 3600,
});
您可以傳遞以下任何 ACL
ACL | 說明 |
---|---|
"public-read" | 物件可由公眾讀取。 |
"private" | 物件只能由儲存桶擁有者讀取。 |
"public-read-write" | 物件可由公眾讀取和寫入。 |
"authenticated-read" | 物件可由儲存桶擁有者和已驗證的使用者讀取。 |
"aws-exec-read" | 物件可由發出請求的 AWS 帳戶讀取。 |
"bucket-owner-read" | 物件可由儲存桶擁有者讀取。 |
"bucket-owner-full-control" | 物件可由儲存桶擁有者讀取和寫入。 |
"log-delivery-write" | 物件可由用於日誌傳遞的 AWS 服務寫入。 |
過期 URL
若要為預先簽署 URL 設定過期時間,請傳遞 expiresIn
選項。
const url = s3file.presign({
// Seconds
expiresIn: 3600, // 1 hour
// access control list
acl: "public-read",
// HTTP method
method: "PUT",
});
method
若要為預先簽署 URL 設定 HTTP 方法,請傳遞 method
選項。
const url = s3file.presign({
method: "PUT",
// method: "DELETE",
// method: "GET",
// method: "HEAD",
// method: "POST",
// method: "PUT",
});
new Response(S3File)
若要快速將使用者重新導向至 S3 檔案的預先簽署 URL,請將 S3File
實例作為主體傳遞給 Response
物件。
const response = new Response(s3file);
console.log(response);
這會自動將使用者重新導向至 S3 檔案的預先簽署 URL,為您節省將檔案下載到伺服器並將其發送回使用者的記憶體、時間和頻寬成本。
Response (0 KB) {
ok: false,
url: "",
status: 302,
statusText: "",
headers: Headers {
"location": "https://<account-id>.r2.cloudflarestorage.com/...",
},
redirected: true,
bodyUsed: false
}
支援相容 S3 的服務
Bun 的 S3 實作適用於任何相容 S3 的儲存服務。 只要指定適當的端點即可
將 Bun 的 S3Client 用於 AWS S3
AWS S3 是預設值。 您也可以傳遞 region
選項而不是 endpoint
選項給 AWS S3。
import { S3Client } from "bun";
// AWS S3
const s3 = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// region: "us-east-1",
});
將 Bun 的 S3Client 用於 Google Cloud Storage
若要將 Bun 的 S3 用戶端與 Google Cloud Storage 搭配使用,請在 S3Client
建構函式中將 endpoint
設定為 "https://storage.googleapis.com"
。
import { S3Client } from "bun";
// Google Cloud Storage
const gcs = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
bucket: "my-bucket",
endpoint: "https://storage.googleapis.com",
});
將 Bun 的 S3Client 用於 Cloudflare R2
若要將 Bun 的 S3 用戶端與 Cloudflare R2 搭配使用,請在 S3Client
建構函式中將 endpoint
設定為 R2 端點。 R2 端點包含您的帳戶 ID。
import { S3Client } from "bun";
// CloudFlare R2
const r2 = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
bucket: "my-bucket",
endpoint: "https://<account-id>.r2.cloudflarestorage.com",
});
將 Bun 的 S3Client 用於 DigitalOcean Spaces
若要將 Bun 的 S3 用戶端與 DigitalOcean Spaces 搭配使用,請在 S3Client
建構函式中將 endpoint
設定為 DigitalOcean Spaces 端點。
import { S3Client } from "bun";
const spaces = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
bucket: "my-bucket",
// region: "nyc3",
endpoint: "https://<region>.digitaloceanspaces.com",
});
將 Bun 的 S3Client 用於 MinIO
若要將 Bun 的 S3 用戶端與 MinIO 搭配使用,請在 S3Client
建構函式中將 endpoint
設定為 MinIO 正在執行的 URL。
import { S3Client } from "bun";
const minio = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
bucket: "my-bucket",
// Make sure to use the correct endpoint URL
// It might not be localhost in production!
endpoint: "https://127.0.0.1:9000",
});
將 Bun 的 S3Client 用於 supabase
若要將 Bun 的 S3 用戶端與 supabase 搭配使用,請在 S3Client
建構函式中將 endpoint
設定為 supabase 端點。 supabase 端點包含您的帳戶 ID 和 /storage/v1/s3 路徑。 請務必在 supabase 儀表板中的 https://supabase.com/dashboard/project/<account-id>/settings/storage 上設定啟用透過 S3 協定連線,並設定在同一區段中告知的區域。
import { S3Client } from "bun";
const supabase = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
bucket: "my-bucket",
region: "us-west-1",
endpoint: "https://<account-id>.supabase.co/storage/v1/s3/storage",
});
將 Bun 的 S3Client 用於 S3 虛擬託管樣式端點
當使用 S3 虛擬託管樣式端點時,您需要將 virtualHostedStyle
選項設定為 true
,如果未提供端點,Bun 將使用區域和儲存桶來推斷 AWS S3 的端點,如果未提供區域,則將使用 us-east-1
。 如果您提供端點,則無需提供儲存桶名稱。
import { S3Client } from "bun";
// AWS S3 endpoint inferred from region and bucket
const s3 = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
bucket: "my-bucket",
virtualHostedStyle: true,
// endpoint: "https://my-bucket.s3.us-east-1.amazonaws.com",
// region: "us-east-1",
});
// AWS S3
const s3WithEndpoint = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
endpoint: "https://<bucket-name>.s3.<region>.amazonaws.com",
virtualHostedStyle: true,
});
// Cloudflare R2
const r2WithEndpoint = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
endpoint: "https://<bucket-name>.<account-id>.r2.cloudflarestorage.com",
virtualHostedStyle: true,
});
憑證
憑證是使用 S3 最困難的部分之一,我們已盡力使其盡可能容易。 預設情況下,Bun 會讀取以下環境變數以取得憑證。
選項名稱 | 環境變數 |
---|---|
accessKeyId | S3_ACCESS_KEY_ID |
secretAccessKey | S3_SECRET_ACCESS_KEY |
region | S3_REGION |
endpoint | S3_ENDPOINT |
bucket | S3_BUCKET |
sessionToken | S3_SESSION_TOKEN |
如果未設定 S3_*
環境變數,Bun 也會檢查上述每個選項的 AWS_*
環境變數。
選項名稱 | 備用環境變數 |
---|---|
accessKeyId | AWS_ACCESS_KEY_ID |
secretAccessKey | AWS_SECRET_ACCESS_KEY |
region | AWS_REGION |
endpoint | AWS_ENDPOINT |
bucket | AWS_BUCKET |
sessionToken | AWS_SESSION_TOKEN |
這些環境變數是從 .env
檔案 或初始化時的程序環境讀取的(process.env
不用於此)。
這些預設值會被您傳遞給 s3.file(credentials)
、new Bun.S3Client(credentials)
或任何接受憑證的方法的選項覆寫。 因此,舉例來說,如果您對不同的儲存桶使用相同的憑證,您可以將憑證設定一次在您的 .env
檔案中,然後將 bucket: "my-bucket"
傳遞給 s3.file()
函數,而無需再次指定所有憑證。
S3Client
物件
當您不使用環境變數或使用多個儲存桶時,您可以建立 S3Client
物件以明確設定憑證。
import { S3Client } from "bun";
const client = new S3Client({
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// sessionToken: "..."
endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
// endpoint: "https://127.0.0.1:9000", // MinIO
});
// Write using a Response
await file.write(new Response("Hello World!"));
// Presign a URL
const url = file.presign({
expiresIn: 60 * 60 * 24, // 1 day
acl: "public-read",
});
// Delete the file
await file.delete();
S3Client.prototype.write
若要上傳或寫入檔案至 S3,請在 S3Client
實例上呼叫 write
。
const client = new Bun.S3Client({
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
endpoint: "https://s3.us-east-1.amazonaws.com",
bucket: "my-bucket",
});
await client.write("my-file.txt", "Hello World!");
await client.write("my-file.txt", new Response("Hello World!"));
// equivalent to
// await client.file("my-file.txt").write("Hello World!");
S3Client.prototype.delete
若要從 S3 刪除檔案,請在 S3Client
實例上呼叫 delete
。
const client = new Bun.S3Client({
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
});
await client.delete("my-file.txt");
// equivalent to
// await client.file("my-file.txt").delete();
S3Client.prototype.exists
若要檢查檔案是否存在於 S3 中,請在 S3Client
實例上呼叫 exists
。
const client = new Bun.S3Client({
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
});
const exists = await client.exists("my-file.txt");
// equivalent to
// const exists = await client.file("my-file.txt").exists();
S3File
S3File
實例是透過呼叫 S3Client
實例方法或 s3.file()
函數建立的。如同 Bun.file()
,S3File
實例是延遲載入的。它們不一定在建立時就指向實際存在的東西。這就是為什麼所有不涉及網路請求的方法都是完全同步的。
interface S3File extends Blob {
slice(start: number, end?: number): S3File;
exists(): Promise<boolean>;
unlink(): Promise<void>;
presign(options: S3Options): string;
text(): Promise<string>;
json(): Promise<any>;
bytes(): Promise<Uint8Array>;
arrayBuffer(): Promise<ArrayBuffer>;
stream(options: S3Options): ReadableStream;
write(
data:
| string
| Uint8Array
| ArrayBuffer
| Blob
| ReadableStream
| Response
| Request,
options?: BlobPropertyBag,
): Promise<number>;
exists(options?: S3Options): Promise<boolean>;
unlink(options?: S3Options): Promise<void>;
delete(options?: S3Options): Promise<void>;
presign(options?: S3Options): string;
stat(options?: S3Options): Promise<S3Stat>;
/**
* Size is not synchronously available because it requires a network request.
*
* @deprecated Use `stat()` instead.
*/
size: NaN;
// ... more omitted for brevity
}
如同 Bun.file()
,S3File
擴展了 Blob
,因此所有在 Blob
上可用的方法也都在 S3File
上可用。從本機檔案讀取資料的相同 API 也可用於從 S3 讀取資料。
方法 | 輸出 |
---|---|
await s3File.text() | 字串 |
await s3File.bytes() | Uint8Array |
await s3File.json() | JSON |
await s3File.stream() | ReadableStream |
await s3File.arrayBuffer() | ArrayBuffer |
這表示將 S3File
實例與 fetch()
、Response
和其他接受 Blob
實例的 Web API 一起使用,就能正常運作。
使用 slice
進行部分讀取
若要讀取檔案的部分範圍,您可以使用 slice
方法。
const partial = s3file.slice(0, 1024);
// Read the partial range as a Uint8Array
const bytes = await partial.bytes();
// Read the partial range as a string
const text = await partial.text();
在內部,這是透過使用 HTTP Range
標頭來僅請求您想要的位元組來運作。此 slice
方法與 Blob.prototype.slice
相同。
從 S3 刪除檔案
若要從 S3 刪除檔案,您可以使用 delete
方法。
await s3file.delete();
// await s3File.unlink();
delete
與 unlink
相同。
錯誤代碼
當 Bun 的 S3 API 拋出錯誤時,它將會有一個 code
屬性,該屬性符合以下值之一
ERR_S3_MISSING_CREDENTIALS
ERR_S3_INVALID_METHOD
ERR_S3_INVALID_PATH
ERR_S3_INVALID_ENDPOINT
ERR_S3_INVALID_SIGNATURE
ERR_S3_INVALID_SESSION_TOKEN
當 S3 物件儲存服務傳回錯誤時(也就是說,不是 Bun 的錯誤),它將會是一個 S3Error
實例(一個名稱為 "S3Error"
的 Error
實例)。
S3Client
靜態方法
S3Client
類別提供了幾個與 S3 互動的靜態方法。
S3Client.presign
(靜態)
若要為 S3 檔案產生預先簽署的 URL,您可以使用 S3Client.presign
靜態方法。
import { S3Client } from "bun";
const credentials = {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};
const url = S3Client.presign("my-file.txt", {
...credentials,
expiresIn: 3600,
});
這等同於呼叫 new S3Client(credentials).presign("my-file.txt", { expiresIn: 3600 })
。
S3Client.exists
(靜態)
若要檢查 S3 檔案是否存在,您可以使用 S3Client.exists
靜態方法。
import { S3Client } from "bun";
const credentials = {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
};
const exists = await S3Client.exists("my-file.txt", credentials);
相同的方法也適用於 S3File
實例。
import { s3 } from "bun";
const s3file = s3.file("my-file.txt", {
...credentials,
});
const exists = await s3file.exists();
S3Client.stat
(靜態)
若要取得 S3 檔案的大小、etag 和其他 metadata,您可以使用 S3Client.stat
靜態方法。
import { S3Client } from "bun";
const credentials = {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
};
const stat = await S3Client.stat("my-file.txt", credentials);
// {
// etag: "\"7a30b741503c0b461cc14157e2df4ad8\"",
// lastModified: 2025-01-07T00:19:10.000Z,
// size: 1024,
// type: "text/plain;charset=utf-8",
// }
S3Client.delete
(靜態)
若要刪除 S3 檔案,您可以使用 S3Client.delete
靜態方法。
import { S3Client } from "bun";
const credentials = {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
};
await S3Client.delete("my-file.txt", credentials);
// equivalent to
// await new S3Client(credentials).delete("my-file.txt");
// S3Client.unlink is alias of S3Client.delete
await S3Client.unlink("my-file.txt", credentials);
s3:// 協定
為了更輕鬆地對本機檔案和 S3 檔案使用相同的程式碼,fetch
和 Bun.file()
中支援 s3://
協定。
const response = await fetch("s3://my-bucket/my-file.txt");
const file = Bun.file("s3://my-bucket/my-file.txt");
您可以額外將 s3
選項傳遞給 fetch
和 Bun.file
函數。
const response = await fetch("s3://my-bucket/my-file.txt", {
s3: {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
endpoint: "https://s3.us-east-1.amazonaws.com",
},
headers: {
"range": "bytes=0-1023",
},
});
UTF-8、UTF-16 和 BOM(位元組順序記號)
如同 Response
和 Blob
,S3File
預設採用 UTF-8 編碼。
當在 S3File
上呼叫 text()
或 json()
方法之一時
- 當偵測到 UTF-16 位元組順序記號 (BOM) 時,它將被視為 UTF-16。JavaScriptCore 原生支援 UTF-16,因此它會跳過 UTF-8 轉碼過程(並移除 BOM)。這在大多數情況下是好的,但這確實意味著如果您的 UTF-16 字串中有無效的代理配對字元,它們將會傳遞到 JavaScriptCore(與原始碼相同)。
- 當偵測到 UTF-8 BOM 時,它會在字串傳遞到 JavaScriptCore 之前被移除,並且無效的 UTF-8 碼位會被替換為 Unicode 替換字元 (
\uFFFD
)。 - 不支援 UTF-32。