Azure Blob Storage

Lightweight, dependency-free fake of Azure Blob Storage that speaks the real Azure Blob REST API (XML wire protocol + x-ms-* headers), so application code using @azure/storage-blob can run against it with zero cost and zero side effects.

KeyValue
Port4590
ProtocolAzure Blob Storage REST API (HTTP + XML)
Compatible client@azure/storage-blob (v12)
API version2025-05-05
Size~96 KB
Startup< 100ms
StateIn-memory, ephemeral, resettable

Quick Start

Start the server:

import { AzureblobServer } from "./services/azureblob/src/server.js";

const server = new AzureblobServer(4590);
await server.start();
// ... use it ...
await server.stop();

Connect with the real Azure SDK client. The fake uses path-style addressing (like Azurite), so the blob endpoint always includes the account name:

import { BlobServiceClient, StorageSharedKeyCredential } from "@azure/storage-blob";

const account = "devstoreaccount1";
const key =
  "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";

const credential = new StorageSharedKeyCredential(account, key);
const svc = new BlobServiceClient(
  `http://127.0.0.1:4590/${account}`,
  credential,
);

const container = svc.getContainerClient("my-container");
await container.create();

const blob = container.getBlockBlobClient("hello.txt");
await blob.upload("hello parlel", 12);

const dl = await blob.download();
// dl.readableStreamBody -> "hello parlel"

You can also connect via a connection string:

const conn =
  "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;" +
  "AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;" +
  "BlobEndpoint=http://127.0.0.1:4590/devstoreaccount1;";

const svc = BlobServiceClient.fromConnectionString(conn);

Note: the fake does not validate the Shared Key signature — any account name / key is accepted. The well-known Azurite dev account above is provided for convenience and copy-paste compatibility.

Addressing

URLs are path-style: http://127.0.0.1:4590/{account}/{container}/{blob}?{comp}=...

Implemented Operations

Service (BlobServiceClient)

OperationHTTPEndpoint
Get Service PropertiesGET/{account}/?restype=service&comp=properties
Set Service PropertiesPUT/{account}/?restype=service&comp=properties
Get Account InformationGET/{account}/?comp=properties&restype=account
Get Service StatisticsGET/{account}/?comp=stats
List ContainersGET/{account}/?comp=list
Find Blobs by TagsGET/{account}/?comp=blobs&where=...
Undelete ContainerPUT/{account}/{container}?restype=container&comp=undelete
Submit BatchPOST/{account}/?comp=batch

Container (ContainerClient)

OperationHTTPEndpoint
Create ContainerPUT/{account}/{container}?restype=container
Get Container Properties / existsGET/HEAD/{account}/{container}?restype=container
Delete ContainerDELETE/{account}/{container}?restype=container
Set Container MetadataPUT/{account}/{container}?restype=container&comp=metadata
Get Container ACLGET/{account}/{container}?restype=container&comp=acl
Set Container ACLPUT/{account}/{container}?restype=container&comp=acl
Lease ContainerPUT/{account}/{container}?restype=container&comp=lease
List Blobs (flat + hierarchy)GET/{account}/{container}?restype=container&comp=list

Blob (BlobClient)

OperationHTTPEndpoint
Download / Get Blob (range, conditional)GET/{account}/{container}/{blob}
Get Blob Properties / existsHEAD/{account}/{container}/{blob}
Delete BlobDELETE/{account}/{container}/{blob}
Undelete BlobPUT/{account}/{container}/{blob}?comp=undelete
Set HTTP HeadersPUT/{account}/{container}/{blob}?comp=properties
Set MetadataPUT/{account}/{container}/{blob}?comp=metadata
Set TagsPUT/{account}/{container}/{blob}?comp=tags
Get TagsGET/{account}/{container}/{blob}?comp=tags
Set Access TierPUT/{account}/{container}/{blob}?comp=tier
Create SnapshotPUT/{account}/{container}/{blob}?comp=snapshot
Lease BlobPUT/{account}/{container}/{blob}?comp=lease
Copy From URL (sync)PUT/{account}/{container}/{blob} (x-ms-copy-source)
Begin Copy From URL (async)PUT/{account}/{container}/{blob} (x-ms-copy-source)
Abort CopyPUT/{account}/{container}/{blob}?comp=copy&copyid=...

Block Blob (BlockBlobClient)

OperationHTTPEndpoint
Upload (Put Blob)PUT/{account}/{container}/{blob} (x-ms-blob-type: BlockBlob)
Stage BlockPUT/{account}/{container}/{blob}?comp=block&blockid=...
Stage Block From URLPUT?comp=block&blockid=... (x-ms-copy-source)
Commit Block ListPUT/{account}/{container}/{blob}?comp=blocklist
Get Block ListGET/{account}/{container}/{blob}?comp=blocklist

uploadData, uploadStream, uploadFile, downloadToBuffer all work via the above (large uploads are automatically split into staged blocks by the SDK).

Append Blob (AppendBlobClient)

OperationHTTPEndpoint
CreatePUT/{account}/{container}/{blob} (x-ms-blob-type: AppendBlob)
Append BlockPUT/{account}/{container}/{blob}?comp=appendblock
Append Block From URLPUT?comp=appendblock (x-ms-copy-source)
SealPUT/{account}/{container}/{blob}?comp=seal

Page Blob (PageBlobClient)

OperationHTTPEndpoint
CreatePUT/{account}/{container}/{blob} (x-ms-blob-type: PageBlob)
Upload PagesPUT/{account}/{container}/{blob}?comp=page (x-ms-page-write: update)
Upload Pages From URLPUT?comp=page (x-ms-copy-source)
Clear PagesPUT/{account}/{container}/{blob}?comp=page (x-ms-page-write: clear)
Get Page RangesGET/{account}/{container}/{blob}?comp=pagelist
ResizePUT/{account}/{container}/{blob}?comp=properties (x-ms-blob-content-length)

Lease (BlobLeaseClient)

Acquire, Renew, Change, Release, Break — for both blobs and containers via comp=lease and the x-ms-lease-action header.

Batch (BlobBatchClient)

multipart/mixed sub-request batching at comp=batch:

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureStatus
Containers: create / delete / list / metadata / ACL / lease✅ Supported
Block blobs: upload / download / stage / commit / block list✅ Supported
Append blobs: create / append / append-from-URL / seal✅ Supported
Page blobs: create / pages / ranges / clear / resize✅ Supported
Blob metadata, HTTP headers, tags, access tier✅ Supported
Snapshots (createSnapshot, withSnapshot)✅ Supported
Copy: sync (syncCopyFromURL), async (beginCopyFromURL), abort✅ Supported
*FromURL family (stage / append / pages)✅ Supported
Soft delete + undelete (blob & container)✅ Supported
Delete blob with x-ms-delete-snapshots (include / only)✅ Supported
Find blobs by tags✅ Supported
Range reads + conditional headers (If-Match / If-None-Match / If-(Un)Modified-Since)✅ Supported
Leases (blob + container, all actions)✅ Supported
Batch delete / set-tier✅ Supported
Shared Key signature validation✓ By design — Not enforced (any key accepted)
SAS token generation / validation✓ By design — Not enforced
User delegation keys⟳ Roadmap
Object/container immutability & legal hold✓ By design — No-op / not enforced
Encryption scopes (real crypto)✓ By design — Any non-empty credential is accepted — no real secrets needed
Lease expiry timers (duration countdown)⟳ Roadmap — Leases never auto-expire
Blob versioning (x-ms-version-id)⟳ Roadmap — Snapshots only
Page range diff / incremental copy⟳ Roadmap
Geo-replication (real)⟳ Roadmap — Stats reported as live only

Error Codes & Shapes

Errors are returned as Azure-style XML with a matching x-ms-error-code response header:

<?xml version="1.0" encoding="utf-8"?>
<Error><Code>BlobNotFound</Code><Message>The specified blob does not exist.</Message></Error>
HTTPCodeWhen
400InvalidResourceNameInvalid container name
400Md5MismatchContent-MD5 did not match the body
400InvalidBlockListCommit referenced an unknown block
404ContainerNotFoundOperation on a missing container
404BlobNotFoundOperation on a missing blob
404CannotVerifyCopySourceCopy/*FromURL source not found
409ContainerAlreadyExistsCreate on an existing container
409InvalidBlobTypeWrong blob-type for the operation
409LeaseAlreadyPresentAcquire on an already-leased resource
409LeaseIdMismatchWithLeaseOperationLease id did not match
409SnapshotsPresentDelete blob with snapshots and no x-ms-delete-snapshots
412ConditionNotMetConditional header precondition failed
412AppendPositionConditionNotMetx-ms-blob-condition-appendpos mismatch
416InvalidRangeRequested range outside the blob

Health & Reset

Non-Azure internal endpoints for orchestration:

<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

AZURE_STORAGE_ACCOUNT=devstoreaccount1
AZURE_STORAGE_KEY=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:4590/devstoreaccount1;
<!-- parlel:testenv:end -->