azure-storage-blob-ts
Azure Blob Storage JavaScript/TypeScript SDK (@azure/storage-blob) for blob operations. Use for uploading, downloading, listing, and managing blobs and containers. Supports block blobs, append blobs, page blobs, SAS tokens, and streaming. Triggers: "blob storage", "@azure/storage
Install
npx skills add https://github.com/microsoft/skills/tree/main/.github/plugins/azure-sdk-typescript/skills/azure-storage-blob-ts
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install microsoft-skills@llmmart
git clone https://github.com/microsoft/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole microsoft/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
@azure/storage-blob (TypeScript/JavaScript)
SDK for Azure Blob Storage operations — upload, download, list, and manage blobs and containers.
Installation
npm install @azure/storage-blob @azure/identity
Current Version: 12.x
Node.js: >= 18.0.0
Environment Variables
AZURE_STORAGE_ACCOUNT_NAME=<account-name>
AZURE_STORAGE_ACCOUNT_KEY=<account-key>
# OR connection string
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...
AZURE_TOKEN_CREDENTIALS=prod # Required only if DefaultAzureCredential is used in production
Authentication
Microsoft Entra Token Credential (Recommended)
import { BlobServiceClient } from "@azure/storage-blob";
import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";
// Local dev: DefaultAzureCredential. Production: set AZURE_TOKEN_CREDENTIALS=prod or AZURE_TOKEN_CREDENTIALS=<specific_credential>
const credential = new DefaultAzureCredential({requiredEnvVars: ["AZURE_TOKEN_CREDENTIALS"]});
// Or use a specific credential directly in production:
// See https://learn.microsoft.com/javascript/api/overview/azure/identity-readme?view=azure-node-latest#credential-classes
// const credential = new ManagedIdentityCredential();
const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME!;
const client = new BlobServiceClient(
`https://${accountName}.blob.core.windows.net`,
credential
);
Connection String
import { BlobServiceClient } from "@azure/storage-blob";
const client = BlobServiceClient.fromConnectionString(
process.env.AZURE_STORAGE_CONNECTION_STRING!
);
StorageSharedKeyCredential (Node.js only)
import { BlobServiceClient, StorageSharedKeyCredential } from "@azure/storage-blob";
const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME!;
const accountKey = process.env.AZURE_STORAGE_ACCOUNT_KEY!;
const sharedKeyCredential = new StorageSharedKeyCredential(accountName, accountKey);
const client = new BlobServiceClient(
`https://${accountName}.blob.core.windows.net`,
sharedKeyCredential
);
SAS Token
import { BlobServiceClient } from "@azure/storage-blob";
const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME!;
const sasToken = process.env.AZURE_STORAGE_SAS_TOKEN!; // starts with "?"
const client = new BlobServiceClient(
`https://${accountName}.blob.core.windows.net${sasToken}`
);
Client Hierarchy
BlobServiceClient (account level)
└── ContainerClient (container level)
└── BlobClient (blob level)
├── BlockBlobClient (block blobs - most common)
├── AppendBlobClient (append-only blobs)
└── PageBlobClient (page blobs - VHDs)
Container Operations
Create Container
const containerClient = client.getContainerClient("my-container");
await containerClient.create();
// Or create if not exists
await containerClient.createIfNotExists();
List Containers
for await (const container of client.listContainers()) {
console.log(container.name);
}
// With prefix filter
for await (const container of client.listContainers({ prefix: "logs-" })) {
console.log(container.name);
}
Delete Container
await containerClient.delete();
// Or delete if exists
await containerClient.deleteIfExists();
Blob Operations
Upload Blob (Simple)
const containerClient = client.getContainerClient("my-container");
const blockBlobClient = containerClient.getBlockBlobClient("my-file.txt");
// Upload string
await blockBlobClient.upload("Hello, World!", 13);
// Upload Buffer
const buffer = Buffer.from("Hello, World!");
await blockBlobClient.upload(buffer, buffer.length);
Upload from File (Node.js only)
const blockBlobClient = containerClient.getBlockBlobClient("uploaded-file.txt");
await blockBlobClient.uploadFile("/path/to/local/file.txt");
Upload from Stream (Node.js only)
import * as fs from "fs";
const blockBlobClient = containerClient.getBlockBlobClient("streamed-file.txt");
const readStream = fs.createReadStream("/path/to/local/file.txt");
await blockBlobClient.uploadStream(readStream, 4 * 1024 * 1024, 5, {
// bufferSize: 4MB, maxConcurrency: 5
onProgress: (progress) => console.log(`Uploaded ${progress.loadedBytes} bytes`),
});
Upload from Browser
const blockBlobClient = containerClient.getBlockBlobClient("browser-upload.txt");
// From File input
const fileInput = document.getElementById("fileInput") as HTMLInputElement;
const file = fileInput.files![0];
await blockBlobClient.uploadData(file);
// From Blob/ArrayBuffer
const arrayBuffer = new ArrayBuffer(1024);
await blockBlobClient.uploadData(arrayBuffer);
Download Blob
const blobClient = containerClient.getBlobClient("my-file.txt");
const downloadResponse = await blobClient.download();
// Read as string (browser & Node.js)
const downloaded = await streamToText(downloadResponse.readableStreamBody!);
async function streamToText(readable: NodeJS.ReadableStream): Promise<string> {
const chunks: Buffer[] = [];
for await (const chunk of readable) {
chunks.push(Buffer.from(chunk));
}
return Buffer.concat(chunks).toString("utf-8");
}
Download to File (Node.js only)
const blockBlobClient = containerClient.getBlockBlobClient("my-file.txt");
await blockBlobClient.downloadToFile("/path/to/local/destination.txt");
Download to Buffer (Node.js only)
const blockBlobClient = containerClient.getBlockBlobClient("my-file.txt");
const buffer = await blockBlobClient.downloadToBuffer();
console.log(buffer.toString());
List Blobs
// List all blobs
for await (const blob of containerClient.listBlobsFlat()) {
console.log(blob.name, blob.properties.contentLength);
}
// List with prefix
for await (const blob of containerClient.listBlobsFlat({ prefix: "logs/" })) {
console.log(blob.name);
}
// List by hierarchy (virtual directories)
for await (const item of containerClient.listBlobsByHierarchy("/")) {
if (item.kind === "prefix") {
console.log(`Directory: ${item.name}`);
} else {
console.log(`Blob: ${item.name}`);
}
}
Delete Blob
const blobClient = containerClient.getBlobClient("my-file.txt");
await blobClient.delete();
// Delete if exists
await blobClient.deleteIfExists();
// Delete with snapshots
await blobClient.delete({ deleteSnapshots: "include" });
Copy Blob
const sourceBlobClient = containerClient.getBlobClient("source.txt");
const destBlobClient = containerClient.getBlobClient("destination.txt");
// Start copy operation
const copyPoller = await destBlobClient.beginCopyFromURL(sourceBlobClient.url);
await copyPoller.pollUntilDone();
Blob Properties & Metadata
Get Properties
const blobClient = containerClient.getBlobClient("my-file.txt");
const properties = await blobClient.getProperties();
console.log("Content-Type:", properties.contentType);
console.log("Content-Length:", properties.contentLength);
console.log("Last Modified:", properties.lastModified);
console.log("ETag:", properties.etag);
Set Metadata
await blobClient.setMetadata({
author: "John Doe",
category: "documents",
});
Set HTTP Headers
await blobClient.setHTTPHeaders({
blobContentType: "text/plain",
blobCacheControl: "max-age=3600",
blobContentDisposition: "attachment; filename=download.txt",
});
SAS Token Generation (Node.js only)
Generate Blob SAS
import {
BlobSASPermissions,
generateBlobSASQueryParameters,
StorageSharedKeyCredential,
} from "@azure/storage-blob";
const sharedKeyCredential = new StorageSharedKeyCredential(accountName, accountKey);
const sasToken = generateBlobSASQueryParameters(
{
containerName: "my-container",
blobName: "my-file.txt",
permissions: BlobSASPermissions.parse("r"), // read only
startsOn: new Date(),
expiresOn: new Date(Date.now() + 3600 * 1000), // 1 hour
},
sharedKeyCredential
).toString();
const sasUrl = `https://${accountName}.blob.core.windows.net/my-container/my-file.txt?${sasToken}`;
Generate Container SAS
import { ContainerSASPermissions, generateBlobSASQueryParameters } from "@azure/storage-blob";
const sasToken = generateBlobSASQueryParameters(
{
containerName: "my-container",
permissions: ContainerSASPermissions.parse("racwdl"), // read, add, create, write, delete, list
expiresOn: new Date(Date.now() + 24 * 3600 * 1000), // 24 hours
},
sharedKeyCredential
).toString();
Generate Account SAS
import {
AccountSASPermissions,
AccountSASResourceTypes,
AccountSASServices,
generateAccountSASQueryParameters,
} from "@azure/storage-blob";
const sasToken = generateAccountSASQueryParameters(
{
services: AccountSASServices.parse("b").toString(), // blob
resourceTypes: AccountSASResourceTypes.parse("sco").toString(), // service, container, object
permissions: AccountSASPermissions.parse("rwdlacupi"), // all permissions
expiresOn: new Date(Date.now() + 24 * 3600 * 1000),
},
sharedKeyCredential
).toString();
Blob Types
Block Blob (Default)
Most common type for text and binary files.
const blockBlobClient = containerClient.getBlockBlobClient("document.pdf");
await blockBlobClient.uploadFile("/path/to/document.pdf");
Append Blob
Optimized for append operations (logs, audit trails).
const appendBlobClient = containerClient.getAppendBlobClient("app.log");
// Create the append blob
await appendBlobClient.create();
// Append data
await appendBlobClient.appendBlock("Log entry 1\n", 12);
await appendBlobClient.appendBlock("Log entry 2\n", 12);
Page Blob
Fixed-size blobs for random read/write (VHDs).
const pageBlobClient = containerClient.getPageBlobClient("disk.vhd");
// Create 512-byte aligned page blob
await pageBlobClient.create(1024 * 1024); // 1MB
// Write pages (must be 512-byte aligned)
const buffer = Buffer.alloc(512);
await pageBlobClient.uploadPages(buffer, 0, 512);
Error Handling
import { RestError } from "@azure/storage-blob";
try {
await containerClient.create();
} catch (error) {
if (error instanceof RestError) {
switch (error.statusCode) {
case 404:
console.log("Container not found");
break;
case 409:
console.log("Container already exists");
break;
case 403:
console.log("Access denied");
break;
default:
console.error(`Storage error ${error.statusCode}: ${error.message}`);
}
}
throw error;
}
TypeScript Types Reference
import {
// Clients
BlobServiceClient,
ContainerClient,
BlobClient,
BlockBlobClient,
AppendBlobClient,
PageBlobClient,
// Authentication
StorageSharedKeyCredential,
AnonymousCredential,
// SAS
BlobSASPermissions,
ContainerSASPermissions,
AccountSASPermissions,
AccountSASServices,
AccountSASResourceTypes,
generateBlobSASQueryParameters,
generateAccountSASQueryParameters,
// Options & Responses
BlobDownloadResponseParsed,
BlobUploadCommonResponse,
ContainerCreateResponse,
BlobItem,
ContainerItem,
// Errors
RestError,
} from "@azure/storage-blob";
Best Practices
- Use
DefaultAzureCredentialfor local development; useManagedIdentityCredentialorWorkloadIdentityCredentialfor production - Use streaming for large files —
uploadStream/downloadToFilefor files > 256MB - Set appropriate content types — Use
setHTTPHeadersfor correct MIME types - Use SAS tokens for client access — Generate short-lived tokens for browser uploads
- Handle errors gracefully — Check
RestError.statusCodefor specific handling - Use
*IfNotExistsmethods — For idempotent container/blob creation - Close clients — Not required but good practice in long-running apps
Platform Differences
| Feature | Node.js | Browser |
|---|---|---|
StorageSharedKeyCredential |
✅ | ❌ |
uploadFile() |
✅ | ❌ |
uploadStream() |
✅ | ❌ |
downloadToFile() |
✅ | ❌ |
downloadToBuffer() |
✅ | ❌ |
uploadData() |
✅ | ✅ |
| SAS generation | ✅ | ❌ |
| DefaultAzureCredential | ✅ | ❌ |
| Anonymous/SAS access | ✅ | ✅ |
Files (skills)
-
references
-
sas-tokens.md 12.7 KB
# @azure/storage-blob - SAS Token Patterns Reference documentation for generating Shared Access Signatures (SAS) in the Azure Blob Storage TypeScript SDK. **Source**: [Azure SDK for JS - storage-blob](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/storage/storage-blob) --- ## Installation ```bash npm install @azure/storage-blob @azure/identity ``` --- ## SAS Types Overview | SAS Type | Scope | Use Case | |----------|-------|----------| | **Service SAS** | Single blob or container | Grant access to specific resources | | **Account SAS** | Entire storage account | Grant broad access to multiple services | | **User Delegation SAS** | Single blob or container | Most secure, uses Entra ID credentials | --- ## User Delegation SAS (Recommended) Most secure option—uses Entra ID credentials instead of account keys. ### Generate User Delegation Key ```typescript import { BlobServiceClient, generateBlobSASQueryParameters, BlobSASPermissions, SASProtocol, } from "@azure/storage-blob"; import { DefaultAzureCredential } from "@azure/identity"; const credential = new DefaultAzureCredential(); const blobServiceClient = new BlobServiceClient( `https://${accountName}.blob.core.windows.net`, credential ); // Get user delegation key (valid for up to 7 days) const startsOn = new Date(); const expiresOn = new Date(startsOn.valueOf() + 3600 * 1000); // 1 hour const userDelegationKey = await blobServiceClient.getUserDelegationKey( startsOn, expiresOn ); ``` ### Generate User Delegation SAS for Blob ```typescript const containerName = "my-container"; const blobName = "my-blob.txt"; const sasToken = generateBlobSASQueryParameters( { containerName, blobName, permissions: BlobSASPermissions.parse("r"), // read only startsOn, expiresOn, protocol: SASProtocol.Https, }, userDelegationKey, accountName ).toString(); const sasUrl = `https://${accountName}.blob.core.windows.net/${containerName}/${blobName}?${sasToken}`; console.log("SAS URL:", sasUrl); ``` --- ## Service SAS (Account Key) Uses storage account key. Less secure but simpler for some scenarios. ### Generate Blob SAS ```typescript import { BlobServiceClient, generateBlobSASQueryParameters, BlobSASPermissions, StorageSharedKeyCredential, } from "@azure/storage-blob"; const accountName = process.env["STORAGE_ACCOUNT_NAME"]!; const accountKey = process.env["STORAGE_ACCOUNT_KEY"]!; const sharedKeyCredential = new StorageSharedKeyCredential( accountName, accountKey ); const sasToken = generateBlobSASQueryParameters( { containerName: "my-container", blobName: "my-blob.txt", permissions: BlobSASPermissions.parse("racwd"), // read, add, create, write, delete startsOn: new Date(), expiresOn: new Date(Date.now() + 3600 * 1000), // 1 hour }, sharedKeyCredential ).toString(); const sasUrl = `https://${accountName}.blob.core.windows.net/my-container/my-blob.txt?${sasToken}`; ``` ### Generate Container SAS ```typescript import { ContainerSASPermissions, generateBlobSASQueryParameters, } from "@azure/storage-blob"; const containerSasToken = generateBlobSASQueryParameters( { containerName: "my-container", permissions: ContainerSASPermissions.parse("rl"), // read, list startsOn: new Date(), expiresOn: new Date(Date.now() + 86400 * 1000), // 24 hours }, sharedKeyCredential ).toString(); const containerSasUrl = `https://${accountName}.blob.core.windows.net/my-container?${containerSasToken}`; ``` --- ## Account SAS Grants access to multiple services (blob, queue, table, file). ```typescript import { generateAccountSASQueryParameters, AccountSASPermissions, AccountSASServices, AccountSASResourceTypes, StorageSharedKeyCredential, } from "@azure/storage-blob"; const accountSasToken = generateAccountSASQueryParameters( { services: AccountSASServices.parse("btqf").toString(), // blob, table, queue, file resourceTypes: AccountSASResourceTypes.parse("sco").toString(), // service, container, object permissions: AccountSASPermissions.parse("rwdlacupi"), // all permissions startsOn: new Date(), expiresOn: new Date(Date.now() + 3600 * 1000), protocol: SASProtocol.Https, }, sharedKeyCredential ).toString(); const accountSasUrl = `https://${accountName}.blob.core.windows.net?${accountSasToken}`; ``` --- ## SAS Permissions ### Blob Permissions (`BlobSASPermissions`) | Permission | Code | Description | |------------|------|-------------| | Read | `r` | Read blob content and metadata | | Add | `a` | Add blocks to append blob | | Create | `c` | Create new blob | | Write | `w` | Write to blob | | Delete | `d` | Delete blob | | Delete Version | `x` | Delete blob version | | Tag | `t` | Read/write blob tags | | Move | `m` | Move blob | | Execute | `e` | Execute (for DataLake) | | Set Immutability | `i` | Set immutability policy | | Permanent Delete | `y` | Permanently delete (soft-deleted) | ```typescript // Parse from string const permissions = BlobSASPermissions.parse("rwd"); // Build programmatically const permissions = new BlobSASPermissions(); permissions.read = true; permissions.write = true; permissions.delete = true; ``` ### Container Permissions (`ContainerSASPermissions`) Same as blob permissions, plus: | Permission | Code | Description | |------------|------|-------------| | List | `l` | List blobs in container | ```typescript const permissions = ContainerSASPermissions.parse("rl"); // read and list ``` ### Account Permissions (`AccountSASPermissions`) | Permission | Code | Description | |------------|------|-------------| | Read | `r` | Read | | Write | `w` | Write | | Delete | `d` | Delete | | Delete Version | `x` | Delete version | | List | `l` | List | | Add | `a` | Add | | Create | `c` | Create | | Update | `u` | Update | | Process | `p` | Process messages | | Tag | `t` | Tags | | Filter | `f` | Filter by tags | | Set Immutability | `i` | Immutability policy | --- ## SAS Options ### Content Headers Override response headers: ```typescript const sasToken = generateBlobSASQueryParameters( { containerName: "my-container", blobName: "document.pdf", permissions: BlobSASPermissions.parse("r"), expiresOn: new Date(Date.now() + 3600 * 1000), // Override response headers contentDisposition: "attachment; filename=download.pdf", contentType: "application/pdf", cacheControl: "max-age=3600", contentEncoding: "gzip", contentLanguage: "en-US", }, sharedKeyCredential ).toString(); ``` ### IP Restrictions Restrict SAS to specific IP addresses: ```typescript const sasToken = generateBlobSASQueryParameters( { containerName: "my-container", blobName: "my-blob.txt", permissions: BlobSASPermissions.parse("r"), expiresOn: new Date(Date.now() + 3600 * 1000), // Single IP ipRange: { start: "168.1.5.60" }, // IP range // ipRange: { start: "168.1.5.60", end: "168.1.5.70" }, }, sharedKeyCredential ).toString(); ``` ### Protocol Restriction Require HTTPS: ```typescript import { SASProtocol } from "@azure/storage-blob"; const sasToken = generateBlobSASQueryParameters( { containerName: "my-container", blobName: "my-blob.txt", permissions: BlobSASPermissions.parse("r"), expiresOn: new Date(Date.now() + 3600 * 1000), protocol: SASProtocol.Https, // HTTPS only // protocol: SASProtocol.HttpsAndHttp, // Allow both }, sharedKeyCredential ).toString(); ``` ### Blob Versioning Access specific blob version or snapshot: ```typescript const sasToken = generateBlobSASQueryParameters( { containerName: "my-container", blobName: "my-blob.txt", permissions: BlobSASPermissions.parse("r"), expiresOn: new Date(Date.now() + 3600 * 1000), // For snapshots snapshotTime: "2023-01-15T10:30:00.0000000Z", // For versions // versionId: "2023-01-15T10:30:00.0000000Z", }, sharedKeyCredential ).toString(); ``` --- ## Using SAS URLs ### Download with SAS ```typescript // Client-side (browser or Node.js) const response = await fetch(sasUrl); const blob = await response.blob(); ``` ### Upload with SAS (Write Permission) ```typescript const sasUrlWithWrite = `https://${accountName}.blob.core.windows.net/container/blob.txt?${writeSasToken}`; await fetch(sasUrlWithWrite, { method: "PUT", headers: { "x-ms-blob-type": "BlockBlob", "Content-Type": "text/plain", }, body: "Hello, World!", }); ``` ### Create Anonymous Client with SAS ```typescript import { BlobClient } from "@azure/storage-blob"; // No credential needed - SAS in URL const blobClient = new BlobClient(sasUrl); const downloadResponse = await blobClient.download(0); ``` --- ## Complete Example ```typescript import { BlobServiceClient, generateBlobSASQueryParameters, BlobSASPermissions, ContainerSASPermissions, SASProtocol, StorageSharedKeyCredential, } from "@azure/storage-blob"; import { DefaultAzureCredential } from "@azure/identity"; async function generateSasTokens() { const accountName = process.env["STORAGE_ACCOUNT_NAME"]!; const accountKey = process.env["STORAGE_ACCOUNT_KEY"]!; // Method 1: User Delegation SAS (recommended) const credential = new DefaultAzureCredential(); const blobServiceClient = new BlobServiceClient( `https://${accountName}.blob.core.windows.net`, credential ); const startsOn = new Date(); const expiresOn = new Date(startsOn.valueOf() + 3600 * 1000); const userDelegationKey = await blobServiceClient.getUserDelegationKey( startsOn, expiresOn ); const userDelegationSas = generateBlobSASQueryParameters( { containerName: "uploads", blobName: "user-file.txt", permissions: BlobSASPermissions.parse("r"), startsOn, expiresOn, protocol: SASProtocol.Https, }, userDelegationKey, accountName ).toString(); console.log("User Delegation SAS URL:"); console.log(`https://${accountName}.blob.core.windows.net/uploads/user-file.txt?${userDelegationSas}`); // Method 2: Service SAS with account key const sharedKeyCredential = new StorageSharedKeyCredential( accountName, accountKey ); // Read-only SAS for download const readSas = generateBlobSASQueryParameters( { containerName: "public-files", blobName: "document.pdf", permissions: BlobSASPermissions.parse("r"), expiresOn: new Date(Date.now() + 86400 * 1000), // 24 hours contentDisposition: "attachment; filename=download.pdf", protocol: SASProtocol.Https, }, sharedKeyCredential ).toString(); console.log("\nRead SAS URL:"); console.log(`https://${accountName}.blob.core.windows.net/public-files/document.pdf?${readSas}`); // Write SAS for upload const writeSas = generateBlobSASQueryParameters( { containerName: "uploads", blobName: "new-upload.txt", permissions: BlobSASPermissions.parse("cw"), // create, write expiresOn: new Date(Date.now() + 3600 * 1000), // 1 hour protocol: SASProtocol.Https, }, sharedKeyCredential ).toString(); console.log("\nWrite SAS URL:"); console.log(`https://${accountName}.blob.core.windows.net/uploads/new-upload.txt?${writeSas}`); // Container list SAS const listSas = generateBlobSASQueryParameters( { containerName: "public-files", permissions: ContainerSASPermissions.parse("rl"), // read, list expiresOn: new Date(Date.now() + 3600 * 1000), protocol: SASProtocol.Https, }, sharedKeyCredential ).toString(); console.log("\nContainer List SAS URL:"); console.log(`https://${accountName}.blob.core.windows.net/public-files?${listSas}`); } generateSasTokens().catch(console.error); ``` --- ## Best Practices 1. **Prefer User Delegation SAS** - More secure, tied to Entra ID, auditable 2. **Use shortest possible expiry** - Minimize exposure window 3. **Use HTTPS only** - Set `protocol: SASProtocol.Https` 4. **Apply least privilege** - Grant only necessary permissions 5. **Use IP restrictions** - When client IPs are known 6. **Rotate keys regularly** - If using account key SAS 7. **Store SAS securely** - Never expose in client-side code permanently 8. **Set start time** - Prevent "not yet valid" errors due to clock skew 9. **Use stored access policies** - For container-level SAS management --- ## Security Considerations | Risk | Mitigation | |------|------------| | SAS token leaked | Short expiry, IP restrictions, HTTPS only | | Over-privileged access | Least privilege permissions | | Account key compromise | Use User Delegation SAS instead | | Clock skew issues | Set `startsOn` slightly in the past | | Token reuse | Use unique blob names or short expiry | --- ## See Also - [Streaming Patterns](./streaming.md) - Upload/download with SAS - [Official Documentation](https://learn.microsoft.com/azure/storage/common/storage-sas-overview) -
streaming.md 10.2 KB
# @azure/storage-blob - Streaming Patterns Reference documentation for upload/download streaming in the Azure Blob Storage TypeScript SDK. **Source**: [Azure SDK for JS - storage-blob](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/storage/storage-blob) --- ## Installation ```bash npm install @azure/storage-blob @azure/identity ``` --- ## Client Setup ```typescript import { BlobServiceClient, ContainerClient, BlockBlobClient, } from "@azure/storage-blob"; import { DefaultAzureCredential } from "@azure/identity"; const credential = new DefaultAzureCredential(); const accountUrl = `https://${process.env["STORAGE_ACCOUNT_NAME"]}.blob.core.windows.net`; const blobServiceClient = new BlobServiceClient(accountUrl, credential); const containerClient = blobServiceClient.getContainerClient("my-container"); const blobClient = containerClient.getBlockBlobClient("my-blob.txt"); ``` --- ## Download Streaming ### Download to Buffer ```typescript const downloadResponse = await blobClient.download(0); const downloaded = await streamToBuffer(downloadResponse.readableStreamBody!); async function streamToBuffer(readableStream: NodeJS.ReadableStream): Promise<Buffer> { return new Promise((resolve, reject) => { const chunks: Buffer[] = []; readableStream.on("data", (data) => chunks.push(Buffer.from(data))); readableStream.on("end", () => resolve(Buffer.concat(chunks))); readableStream.on("error", reject); }); } ``` ### Download to File (Node.js) ```typescript import fs from "node:fs"; import { pipeline } from "node:stream/promises"; const downloadResponse = await blobClient.download(0); await pipeline( downloadResponse.readableStreamBody!, fs.createWriteStream("./local-file.txt") ); ``` ### Download with Range ```typescript // Download bytes 100-199 (100 bytes total) const downloadResponse = await blobClient.download(100, 100); ``` ### Download with Progress ```typescript const downloadResponse = await blobClient.download(0, undefined, { onProgress: (progress) => { console.log(`Downloaded ${progress.loadedBytes} bytes`); }, }); ``` --- ## Upload Streaming ### Upload from Buffer ```typescript const content = Buffer.from("Hello, World!"); await blobClient.upload(content, content.length); ``` ### Upload from Stream (Node.js) ```typescript import fs from "node:fs"; const fileStream = fs.createReadStream("./large-file.zip"); const fileSize = fs.statSync("./large-file.zip").size; await blobClient.uploadStream(fileStream, fileSize, 4, { onProgress: (progress) => { const percent = ((progress.loadedBytes / fileSize) * 100).toFixed(2); console.log(`Upload progress: ${percent}%`); }, }); ``` ### Upload with Options ```typescript await blobClient.uploadStream(fileStream, fileSize, 4, { // Number of concurrent upload operations concurrency: 4, // Size of each block (4MB default, max 4000MB) bufferSize: 4 * 1024 * 1024, // Progress tracking onProgress: (progress) => { console.log(`Uploaded: ${progress.loadedBytes} bytes`); }, // HTTP headers blobHTTPHeaders: { blobContentType: "application/zip", blobContentEncoding: "gzip", }, // Custom metadata metadata: { uploadedBy: "my-app", version: "1.0", }, // Access tier tier: "Cool", }); ``` --- ## Block Upload (Large Files) For large files, use staged block uploads for better control: ```typescript import { BlockBlobClient, BlockBlobStageBlockOptions } from "@azure/storage-blob"; import { v4 as uuidv4 } from "uuid"; async function uploadLargeFile( blobClient: BlockBlobClient, filePath: string, blockSize = 4 * 1024 * 1024 // 4MB blocks ) { const fileHandle = await fs.promises.open(filePath, "r"); const fileStats = await fileHandle.stat(); const fileSize = fileStats.size; const blockIds: string[] = []; let offset = 0; let blockIndex = 0; try { while (offset < fileSize) { const chunkSize = Math.min(blockSize, fileSize - offset); const buffer = Buffer.alloc(chunkSize); await fileHandle.read(buffer, 0, chunkSize, offset); // Generate block ID (must be base64 encoded, same length) const blockId = Buffer.from( blockIndex.toString().padStart(6, "0") ).toString("base64"); // Stage the block await blobClient.stageBlock(blockId, buffer, buffer.length); blockIds.push(blockId); offset += chunkSize; blockIndex++; console.log(`Staged block ${blockIndex}, offset: ${offset}/${fileSize}`); } // Commit all blocks await blobClient.commitBlockList(blockIds, { blobHTTPHeaders: { blobContentType: "application/octet-stream", }, }); console.log("Upload complete!"); } finally { await fileHandle.close(); } } ``` --- ## Parallel Upload/Download ### Parallel Download to File ```typescript import { BlobClient } from "@azure/storage-blob"; // Downloads in parallel chunks automatically await blobClient.downloadToFile("./local-file.zip", 0, undefined, { // Chunk size for parallel download blockSize: 4 * 1024 * 1024, // Number of parallel downloads concurrency: 4, onProgress: (progress) => { console.log(`Downloaded: ${progress.loadedBytes} bytes`); }, }); ``` ### Parallel Upload from File ```typescript // Uploads in parallel chunks automatically await blobClient.uploadFile("./large-file.zip", { // Chunk size blockSize: 4 * 1024 * 1024, // Parallel uploads concurrency: 4, onProgress: (progress) => { console.log(`Uploaded: ${progress.loadedBytes} bytes`); }, blobHTTPHeaders: { blobContentType: "application/zip", }, }); ``` --- ## Browser Upload For browser environments, use `uploadBrowserData`: ```typescript // From File input const fileInput = document.getElementById("file") as HTMLInputElement; const file = fileInput.files![0]; await blobClient.uploadBrowserData(file, { onProgress: (progress) => { const percent = ((progress.loadedBytes / file.size) * 100).toFixed(2); document.getElementById("progress")!.textContent = `${percent}%`; }, blobHTTPHeaders: { blobContentType: file.type, }, }); // From ArrayBuffer const arrayBuffer = await file.arrayBuffer(); await blobClient.uploadBrowserData(arrayBuffer); // From Blob const blob = new Blob(["Hello, World!"], { type: "text/plain" }); await blobClient.uploadBrowserData(blob); ``` --- ## Abort Operations Cancel long-running uploads/downloads: ```typescript const abortController = new AbortController(); // Set up abort after 30 seconds setTimeout(() => abortController.abort(), 30000); try { await blobClient.uploadFile("./large-file.zip", { abortSignal: abortController.signal, onProgress: (progress) => { console.log(`Progress: ${progress.loadedBytes}`); }, }); } catch (error) { if (error.name === "AbortError") { console.log("Upload was cancelled"); } else { throw error; } } // Manual abort document.getElementById("cancel")?.addEventListener("click", () => { abortController.abort(); }); ``` --- ## Copy Operations ### Copy from URL ```typescript const sourceUrl = "https://source-account.blob.core.windows.net/container/blob"; // Start async copy const copyPoller = await blobClient.beginCopyFromURL(sourceUrl); // Wait for completion const result = await copyPoller.pollUntilDone(); console.log(`Copy completed: ${result.copyStatus}`); // Or copy synchronously (for small blobs) await blobClient.syncCopyFromURL(sourceUrl); ``` ### Copy with Progress ```typescript const copyPoller = await blobClient.beginCopyFromURL(sourceUrl, { onProgress: (state) => { if (state.copyProgress) { const [copied, total] = state.copyProgress.split("/").map(Number); console.log(`Copied: ${copied}/${total} bytes`); } }, }); ``` --- ## Complete Example ```typescript import { BlobServiceClient, ContainerClient, BlockBlobClient, } from "@azure/storage-blob"; import { DefaultAzureCredential } from "@azure/identity"; import fs from "node:fs"; import { pipeline } from "node:stream/promises"; async function uploadAndDownload() { const credential = new DefaultAzureCredential(); const blobServiceClient = new BlobServiceClient( `https://${process.env["STORAGE_ACCOUNT_NAME"]}.blob.core.windows.net`, credential ); const containerClient = blobServiceClient.getContainerClient("demo"); await containerClient.createIfNotExists(); const blobClient = containerClient.getBlockBlobClient("test-file.txt"); // Upload with progress console.log("Uploading..."); const uploadData = Buffer.from("Hello, Azure Blob Storage!"); await blobClient.upload(uploadData, uploadData.length, { onProgress: (progress) => { console.log(`Upload progress: ${progress.loadedBytes} bytes`); }, }); console.log("Upload complete!"); // Download with progress console.log("Downloading..."); const downloadResponse = await blobClient.download(0, undefined, { onProgress: (progress) => { console.log(`Download progress: ${progress.loadedBytes} bytes`); }, }); // Stream to file await pipeline( downloadResponse.readableStreamBody!, fs.createWriteStream("./downloaded-file.txt") ); console.log("Download complete!"); // Get blob properties const properties = await blobClient.getProperties(); console.log(`Blob size: ${properties.contentLength} bytes`); console.log(`Content type: ${properties.contentType}`); console.log(`Last modified: ${properties.lastModified}`); // Cleanup await blobClient.delete(); console.log("Blob deleted"); } uploadAndDownload().catch(console.error); ``` --- ## Best Practices 1. **Use parallel operations** - `uploadFile`/`downloadToFile` automatically parallelize 2. **Set appropriate block sizes** - 4-8MB for most scenarios, larger for huge files 3. **Implement progress tracking** - Use `onProgress` for user feedback 4. **Handle abort signals** - Allow users to cancel long operations 5. **Set content types** - Always set `blobContentType` for proper browser handling 6. **Use streams for large files** - Avoid loading entire files into memory 7. **Implement retry logic** - SDK has built-in retries, configure as needed --- ## See Also - [SAS Token Patterns](./sas-tokens.md) - Generate secure access tokens - [Official Samples](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/storage/storage-blob/samples)
-
-
SKILL.md 12.8 KB
--- name: azure-storage-blob-ts description: | Azure Blob Storage JavaScript/TypeScript SDK (@azure/storage-blob) for blob operations. Use for uploading, downloading, listing, and managing blobs and containers. Supports block blobs, append blobs, page blobs, SAS tokens, and streaming. Triggers: "blob storage", "@azure/storage-blob", "BlobServiceClient", "ContainerClient", "upload blob", "download blob", "SAS token", "block blob". license: MIT metadata: author: Microsoft version: "1.0.0" package: '@azure/storage-blob' --- # @azure/storage-blob (TypeScript/JavaScript) SDK for Azure Blob Storage operations — upload, download, list, and manage blobs and containers. ## Installation ```bash npm install @azure/storage-blob @azure/identity ``` **Current Version**: 12.x **Node.js**: >= 18.0.0 ## Environment Variables ```bash AZURE_STORAGE_ACCOUNT_NAME=<account-name> AZURE_STORAGE_ACCOUNT_KEY=<account-key> # OR connection string AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=... AZURE_TOKEN_CREDENTIALS=prod # Required only if DefaultAzureCredential is used in production ``` ## Authentication ### Microsoft Entra Token Credential (Recommended) ```typescript import { BlobServiceClient } from "@azure/storage-blob"; import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity"; // Local dev: DefaultAzureCredential. Production: set AZURE_TOKEN_CREDENTIALS=prod or AZURE_TOKEN_CREDENTIALS=<specific_credential> const credential = new DefaultAzureCredential({requiredEnvVars: ["AZURE_TOKEN_CREDENTIALS"]}); // Or use a specific credential directly in production: // See https://learn.microsoft.com/javascript/api/overview/azure/identity-readme?view=azure-node-latest#credential-classes // const credential = new ManagedIdentityCredential(); const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME!; const client = new BlobServiceClient( `https://${accountName}.blob.core.windows.net`, credential ); ``` ### Connection String ```typescript import { BlobServiceClient } from "@azure/storage-blob"; const client = BlobServiceClient.fromConnectionString( process.env.AZURE_STORAGE_CONNECTION_STRING! ); ``` ### StorageSharedKeyCredential (Node.js only) ```typescript import { BlobServiceClient, StorageSharedKeyCredential } from "@azure/storage-blob"; const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME!; const accountKey = process.env.AZURE_STORAGE_ACCOUNT_KEY!; const sharedKeyCredential = new StorageSharedKeyCredential(accountName, accountKey); const client = new BlobServiceClient( `https://${accountName}.blob.core.windows.net`, sharedKeyCredential ); ``` ### SAS Token ```typescript import { BlobServiceClient } from "@azure/storage-blob"; const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME!; const sasToken = process.env.AZURE_STORAGE_SAS_TOKEN!; // starts with "?" const client = new BlobServiceClient( `https://${accountName}.blob.core.windows.net${sasToken}` ); ``` ## Client Hierarchy ``` BlobServiceClient (account level) └── ContainerClient (container level) └── BlobClient (blob level) ├── BlockBlobClient (block blobs - most common) ├── AppendBlobClient (append-only blobs) └── PageBlobClient (page blobs - VHDs) ``` ## Container Operations ### Create Container ```typescript const containerClient = client.getContainerClient("my-container"); await containerClient.create(); // Or create if not exists await containerClient.createIfNotExists(); ``` ### List Containers ```typescript for await (const container of client.listContainers()) { console.log(container.name); } // With prefix filter for await (const container of client.listContainers({ prefix: "logs-" })) { console.log(container.name); } ``` ### Delete Container ```typescript await containerClient.delete(); // Or delete if exists await containerClient.deleteIfExists(); ``` ## Blob Operations ### Upload Blob (Simple) ```typescript const containerClient = client.getContainerClient("my-container"); const blockBlobClient = containerClient.getBlockBlobClient("my-file.txt"); // Upload string await blockBlobClient.upload("Hello, World!", 13); // Upload Buffer const buffer = Buffer.from("Hello, World!"); await blockBlobClient.upload(buffer, buffer.length); ``` ### Upload from File (Node.js only) ```typescript const blockBlobClient = containerClient.getBlockBlobClient("uploaded-file.txt"); await blockBlobClient.uploadFile("/path/to/local/file.txt"); ``` ### Upload from Stream (Node.js only) ```typescript import * as fs from "fs"; const blockBlobClient = containerClient.getBlockBlobClient("streamed-file.txt"); const readStream = fs.createReadStream("/path/to/local/file.txt"); await blockBlobClient.uploadStream(readStream, 4 * 1024 * 1024, 5, { // bufferSize: 4MB, maxConcurrency: 5 onProgress: (progress) => console.log(`Uploaded ${progress.loadedBytes} bytes`), }); ``` ### Upload from Browser ```typescript const blockBlobClient = containerClient.getBlockBlobClient("browser-upload.txt"); // From File input const fileInput = document.getElementById("fileInput") as HTMLInputElement; const file = fileInput.files![0]; await blockBlobClient.uploadData(file); // From Blob/ArrayBuffer const arrayBuffer = new ArrayBuffer(1024); await blockBlobClient.uploadData(arrayBuffer); ``` ### Download Blob ```typescript const blobClient = containerClient.getBlobClient("my-file.txt"); const downloadResponse = await blobClient.download(); // Read as string (browser & Node.js) const downloaded = await streamToText(downloadResponse.readableStreamBody!); async function streamToText(readable: NodeJS.ReadableStream): Promise<string> { const chunks: Buffer[] = []; for await (const chunk of readable) { chunks.push(Buffer.from(chunk)); } return Buffer.concat(chunks).toString("utf-8"); } ``` ### Download to File (Node.js only) ```typescript const blockBlobClient = containerClient.getBlockBlobClient("my-file.txt"); await blockBlobClient.downloadToFile("/path/to/local/destination.txt"); ``` ### Download to Buffer (Node.js only) ```typescript const blockBlobClient = containerClient.getBlockBlobClient("my-file.txt"); const buffer = await blockBlobClient.downloadToBuffer(); console.log(buffer.toString()); ``` ### List Blobs ```typescript // List all blobs for await (const blob of containerClient.listBlobsFlat()) { console.log(blob.name, blob.properties.contentLength); } // List with prefix for await (const blob of containerClient.listBlobsFlat({ prefix: "logs/" })) { console.log(blob.name); } // List by hierarchy (virtual directories) for await (const item of containerClient.listBlobsByHierarchy("/")) { if (item.kind === "prefix") { console.log(`Directory: ${item.name}`); } else { console.log(`Blob: ${item.name}`); } } ``` ### Delete Blob ```typescript const blobClient = containerClient.getBlobClient("my-file.txt"); await blobClient.delete(); // Delete if exists await blobClient.deleteIfExists(); // Delete with snapshots await blobClient.delete({ deleteSnapshots: "include" }); ``` ### Copy Blob ```typescript const sourceBlobClient = containerClient.getBlobClient("source.txt"); const destBlobClient = containerClient.getBlobClient("destination.txt"); // Start copy operation const copyPoller = await destBlobClient.beginCopyFromURL(sourceBlobClient.url); await copyPoller.pollUntilDone(); ``` ## Blob Properties & Metadata ### Get Properties ```typescript const blobClient = containerClient.getBlobClient("my-file.txt"); const properties = await blobClient.getProperties(); console.log("Content-Type:", properties.contentType); console.log("Content-Length:", properties.contentLength); console.log("Last Modified:", properties.lastModified); console.log("ETag:", properties.etag); ``` ### Set Metadata ```typescript await blobClient.setMetadata({ author: "John Doe", category: "documents", }); ``` ### Set HTTP Headers ```typescript await blobClient.setHTTPHeaders({ blobContentType: "text/plain", blobCacheControl: "max-age=3600", blobContentDisposition: "attachment; filename=download.txt", }); ``` ## SAS Token Generation (Node.js only) ### Generate Blob SAS ```typescript import { BlobSASPermissions, generateBlobSASQueryParameters, StorageSharedKeyCredential, } from "@azure/storage-blob"; const sharedKeyCredential = new StorageSharedKeyCredential(accountName, accountKey); const sasToken = generateBlobSASQueryParameters( { containerName: "my-container", blobName: "my-file.txt", permissions: BlobSASPermissions.parse("r"), // read only startsOn: new Date(), expiresOn: new Date(Date.now() + 3600 * 1000), // 1 hour }, sharedKeyCredential ).toString(); const sasUrl = `https://${accountName}.blob.core.windows.net/my-container/my-file.txt?${sasToken}`; ``` ### Generate Container SAS ```typescript import { ContainerSASPermissions, generateBlobSASQueryParameters } from "@azure/storage-blob"; const sasToken = generateBlobSASQueryParameters( { containerName: "my-container", permissions: ContainerSASPermissions.parse("racwdl"), // read, add, create, write, delete, list expiresOn: new Date(Date.now() + 24 * 3600 * 1000), // 24 hours }, sharedKeyCredential ).toString(); ``` ### Generate Account SAS ```typescript import { AccountSASPermissions, AccountSASResourceTypes, AccountSASServices, generateAccountSASQueryParameters, } from "@azure/storage-blob"; const sasToken = generateAccountSASQueryParameters( { services: AccountSASServices.parse("b").toString(), // blob resourceTypes: AccountSASResourceTypes.parse("sco").toString(), // service, container, object permissions: AccountSASPermissions.parse("rwdlacupi"), // all permissions expiresOn: new Date(Date.now() + 24 * 3600 * 1000), }, sharedKeyCredential ).toString(); ``` ## Blob Types ### Block Blob (Default) Most common type for text and binary files. ```typescript const blockBlobClient = containerClient.getBlockBlobClient("document.pdf"); await blockBlobClient.uploadFile("/path/to/document.pdf"); ``` ### Append Blob Optimized for append operations (logs, audit trails). ```typescript const appendBlobClient = containerClient.getAppendBlobClient("app.log"); // Create the append blob await appendBlobClient.create(); // Append data await appendBlobClient.appendBlock("Log entry 1\n", 12); await appendBlobClient.appendBlock("Log entry 2\n", 12); ``` ### Page Blob Fixed-size blobs for random read/write (VHDs). ```typescript const pageBlobClient = containerClient.getPageBlobClient("disk.vhd"); // Create 512-byte aligned page blob await pageBlobClient.create(1024 * 1024); // 1MB // Write pages (must be 512-byte aligned) const buffer = Buffer.alloc(512); await pageBlobClient.uploadPages(buffer, 0, 512); ``` ## Error Handling ```typescript import { RestError } from "@azure/storage-blob"; try { await containerClient.create(); } catch (error) { if (error instanceof RestError) { switch (error.statusCode) { case 404: console.log("Container not found"); break; case 409: console.log("Container already exists"); break; case 403: console.log("Access denied"); break; default: console.error(`Storage error ${error.statusCode}: ${error.message}`); } } throw error; } ``` ## TypeScript Types Reference ```typescript import { // Clients BlobServiceClient, ContainerClient, BlobClient, BlockBlobClient, AppendBlobClient, PageBlobClient, // Authentication StorageSharedKeyCredential, AnonymousCredential, // SAS BlobSASPermissions, ContainerSASPermissions, AccountSASPermissions, AccountSASServices, AccountSASResourceTypes, generateBlobSASQueryParameters, generateAccountSASQueryParameters, // Options & Responses BlobDownloadResponseParsed, BlobUploadCommonResponse, ContainerCreateResponse, BlobItem, ContainerItem, // Errors RestError, } from "@azure/storage-blob"; ``` ## Best Practices 1. **Use `DefaultAzureCredential` for local development; use `ManagedIdentityCredential` or `WorkloadIdentityCredential` for production** 2. **Use streaming for large files** — `uploadStream`/`downloadToFile` for files > 256MB 3. **Set appropriate content types** — Use `setHTTPHeaders` for correct MIME types 4. **Use SAS tokens for client access** — Generate short-lived tokens for browser uploads 5. **Handle errors gracefully** — Check `RestError.statusCode` for specific handling 6. **Use `*IfNotExists` methods** — For idempotent container/blob creation 7. **Close clients** — Not required but good practice in long-running apps ## Platform Differences | Feature | Node.js | Browser | |---------|---------|---------| | `StorageSharedKeyCredential` | ✅ | ❌ | | `uploadFile()` | ✅ | ❌ | | `uploadStream()` | ✅ | ❌ | | `downloadToFile()` | ✅ | ❌ | | `downloadToBuffer()` | ✅ | ❌ | | `uploadData()` | ✅ | ✅ | | SAS generation | ✅ | ❌ | | DefaultAzureCredential | ✅ | ❌ | | Anonymous/SAS access | ✅ | ✅ |
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.