This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Dapr Binary Store JavaScript SDK

Store and retrieve binary files using the Dapr JavaScript SDK

The Dapr JavaScript SDK lets Node.js applications store, retrieve, stream, and delete binary files in a configured Dapr binary store component. Use DaprClient.binaryStore with either HTTP or gRPC to stream large files or work with bytes already held in memory.

Binary Store requires Dapr runtime 1.19 or later and JavaScript SDK 3.19.0 or later. The underlying Binary Store API is alpha.

To get started, follow the Binary Store how-to guide. See the client usage guidance for configuration, memory use, cancellation, and errors.

1 - How to: Manage binary files with the Dapr JavaScript SDK

Learn how to store, retrieve, stream, and delete binary files using the Dapr JavaScript SDK

Use DaprClient.binaryStore to store and retrieve files through a Dapr binary store component. The client supports binary streams so you can transfer large files without loading the entire file into your application’s memory. This guide covers component configuration, uploads, downloads, overwrites, and deletes.

Prerequisites

In your application directory, initialize an npm project if you do not already have one, then install the SDK:

npm init -y
npm install @dapr/dapr@3.19.0

The examples use JavaScript ES modules and run as a .mjs file without a TypeScript compiler. The same APIs are available in TypeScript.

Configure an in-memory binary store

Create a components directory in your application directory and save the following as components\filestore.yaml:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: filestore
spec:
  type: binarystore.in-memory
  version: v1
  metadata:
    - name: maxSize
      value: "4Mi"
    - name: maxTotalSize
      value: "512Mi"

This component stores files in the sidecar’s memory, with a per-file limit of 4 MiB and a combined capacity of 512 MiB. Files are lost when the sidecar stops and are not shared across sidecars. Use it for local development and testing, not durable storage. See the in-memory component reference for capacity settings.

The component’s metadata.name, filestore, is the store name passed to every client operation. It is not the component’s type or the YAML file name.

Upload and download a file

Place a report.pdf file no larger than 4 MiB in your application’s working directory. Save the following as app.mjs:

import { createReadStream, createWriteStream } from "node:fs";
import { CommunicationProtocolEnum, DaprClient } from "@dapr/dapr";

const client = new DaprClient({
  daprHost: "127.0.0.1",
  daprPort: "3500",
  communicationProtocol: CommunicationProtocolEnum.HTTP,
});

const storeName = "filestore";
const fileName = "uploads/report.pdf";

try {
  await client.binaryStore.setBinaryFile(
    storeName,
    fileName,
    createReadStream("report.pdf"),
  );

  await client.binaryStore.getBinaryFile(
    storeName,
    fileName,
    createWriteStream("downloaded-report.pdf"),
  );

  console.log("Downloaded report.pdf to downloaded-report.pdf");
} finally {
  await client.stop();
}

Start the application with a Dapr sidecar, loading the component from components:

dapr run --app-id binary-store-example --dapr-http-port 3500 --dapr-grpc-port 50001 --resources-path .\components -- node app.mjs

The example uploads report.pdf under the store-relative name uploads/report.pdf and downloads it to downloaded-report.pdf. getBinaryFile waits for the destination stream to finish and ends it on success.

To use gRPC instead, change communicationProtocol to CommunicationProtocolEnum.GRPC and daprPort to "50001". All binary store methods work with either protocol.

The examples below reuse client, storeName, and fileName. Add them inside the try block, after the download and before the client is stopped. Run the delete example last. If you rerun the application against a sidecar that still holds the file, delete it first or explicitly enable overwriting.

Replace an existing file

Uploads do not overwrite existing files by default. To replace a file, pass overwrite: true in the options object:

await client.binaryStore.setBinaryFile(
  storeName,
  fileName,
  createReadStream("report.pdf"),
  { overwrite: true, chunkSize: 64 * 1024 },
);

Uploads use a maximum chunk size of 32 KiB by default. Set chunkSize to another positive safe integer, in bytes, to tune upload chunking. This does not change the component’s per-file or total-capacity limits.

File names can include nested paths such as uploads/report.pdf. Do not use a leading or trailing slash, empty path segments, . or .. segments, or the characters \, *, ?, [, ], and #. Pass the name as plain text, not a URL or a pre-encoded value; the HTTP client URL-encodes it.

Work with in-memory content

For small files already held in memory, upload a Uint8Array or Node.js Buffer. Use getBinaryFileBytes to retrieve the complete file as a Buffer:

const content = Buffer.from([0x00, 0x80, 0xff]);
await client.binaryStore.setBinaryFile(storeName, "data.bin", content);

const downloaded = await client.binaryStore.getBinaryFileBytes(
  storeName,
  "data.bin",
);
console.log("Bytes match:", downloaded.equals(content));

The SDK transfers raw bytes without JSON serialization or text encoding. Empty files are supported; for example, upload Buffer.alloc(0) to create an empty file.

getBinaryFileBytes buffers the entire download in memory. Prefer getBinaryFile or getBinaryFileChunks for large files.

Read a file in chunks

getBinaryFileChunks returns an AsyncIterable<Buffer>. Consume it with for await...of to process the bytes in order without buffering the entire file:

let totalBytes = 0;
for await (const chunk of client.binaryStore.getBinaryFileChunks(
  storeName,
  fileName,
)) {
  totalBytes += chunk.length;
}
console.log("Downloaded bytes:", totalBytes);

Chunk boundaries can differ between protocols and do not necessarily match the upload chunks. Breaking out of the loop closes the download stream.

Delete a file

await client.binaryStore.deleteBinaryFile(storeName, fileName);
await client.binaryStore.deleteBinaryFile(storeName, "data.bin");

A missing file causes downloads and deletes to fail rather than returning empty content or silently succeeding.

Next steps

Read the client usage guidance for cancellation, stream ownership, client configuration, and error handling.

2 - Binary Store client usage

Essential guidance for configuring and using the JavaScript Binary Store client

Binary Store is available through DaprClient.binaryStore in JavaScript SDK 3.19.0 or later and requires Dapr runtime 1.19 or later. Both HTTP and gRPC use the alpha Binary Store API.

Client lifetime and configuration

Reuse a long-lived DaprClient rather than creating a client for each operation. Await outstanding operations before calling await client.stop() during application shutdown.

The default protocol is HTTP. To explicitly configure a gRPC connection:

import { CommunicationProtocolEnum, DaprClient } from "@dapr/dapr";

const client = new DaprClient({
  daprHost: "127.0.0.1",
  daprPort: "50001",
  communicationProtocol: CommunicationProtocolEnum.GRPC,
  daprApiToken: process.env.DAPR_API_TOKEN,
});

Use CommunicationProtocolEnum.HTTP and the sidecar’s HTTP port (by default "3500") for HTTP. Supply port values as strings.

When you omit explicit host and port options, the SDK can use DAPR_HTTP_ENDPOINT or DAPR_GRPC_ENDPOINT for the selected protocol. Otherwise, it uses the configured host and port, falling back to 127.0.0.1 and DAPR_HTTP_PORT or DAPR_GRPC_PORT (defaults: 3500 and 50001). The API token defaults to DAPR_API_TOKEN if daprApiToken is not supplied. Binary store requests use the same authentication settings as other client operations.

Choose an operation

All methods take a component name and a store-relative file name. The optional options object is the last argument.

MethodContent or destinationResult
setBinaryFile(storeName, fileName, content, options?)Uint8Array, Buffer, or binary Node.js ReadablePromise<void>
getBinaryFile(storeName, fileName, destination, options?)Node.js WritablePromise<void>
getBinaryFileBytes(storeName, fileName, options?)No destination; buffers the complete filePromise<Buffer>
getBinaryFileChunks(storeName, fileName, options?)No destination; yields ordered chunksAsyncIterable<Buffer>
deleteBinaryFile(storeName, fileName, options?)No content or destinationPromise<void>

TypeScript applications can import IClientBinaryStore, BinaryStoreOptions, and BinaryStoreSetOptions as types from @dapr/dapr. BinaryStoreOptions contains signal?: AbortSignal. Uploads accept BinaryStoreSetOptions, which also provides overwrite?: boolean and chunkSize?: number.

Streaming and memory use

Use a binary Readable, such as createReadStream, for large uploads. The stream must emit Uint8Array or Buffer chunks, not strings. Do not set a text encoding on the source stream. Once initial validation succeeds, the client consumes the source from its current position and destroys it when the upload finishes or fails. If validation fails or the signal is already aborted before the transfer starts, the caller remains responsible for closing the source. Create a new source stream for each attempt.

The upload chunk size defaults to 32 KiB. A custom chunkSize must be a positive safe integer. Smaller chunks do not bypass the component’s file-size or total-capacity limits.

getBinaryFile uses a stream pipeline that respects destination backpressure. It ends the destination on success and destroys it on failure. Do not reuse that destination for another download. A failed download may leave a partial local file; your application should remove or otherwise handle it.

getBinaryFileChunks lets you process each Buffer as it arrives. Consume it with for await...of; breaking out of the loop closes the response stream. Download chunk sizes are determined by the runtime and transport, not by the upload’s chunkSize.

Use getBinaryFileBytes only when the entire file fits comfortably in memory. It accumulates all chunks and concatenates them into a Buffer, so peak memory use can exceed the size of the file.

Cancellation

Every binary store operation accepts signal in its options object. Pass an AbortSignal to cancel work when the caller disconnects or an application deadline expires. For example, add a 30-second deadline to a download:

import { createWriteStream } from "node:fs";

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30_000);

try {
  await client.binaryStore.getBinaryFile(
    "filestore",
    "uploads/report.pdf",
    createWriteStream("downloaded-report.pdf"),
    { signal: controller.signal },
  );
} finally {
  clearTimeout(timeout);
}

This snippet assumes an existing client. Keep the import at module scope and run the remaining code inside an async function or an ES module. Cancellation rejects the operation and can destroy the associated streams. Do not assume every protocol reports cancellation using the same error class.

For chunk enumeration, pass the signal to getBinaryFileChunks itself. This example creates a fresh 30-second timeout signal:

for await (const chunk of client.binaryStore.getBinaryFileChunks(
  "filestore",
  "uploads/report.pdf",
  { signal: AbortSignal.timeout(30_000) },
)) {
  console.log("Received bytes:", chunk.length);
}

Errors

Await each operation and handle failures at your application’s error boundary. For getBinaryFileChunks, errors surface while iterating, so error handling must surround the for await...of loop.

Runtime failures reject rather than returning success-shaped defaults. Examples include an upload to an existing file without overwrite: true, downloading or deleting a missing file, a missing component, and exceeding component capacity.

  • HTTP: Runtime errors are JavaScript Error instances whose message is a JSON string containing status (HTTP status code), error (status text), and error_msg (runtime response body). Transport and stream errors can have different shapes; do not assume every error message is JSON.
  • gRPC: Runtime errors preserve the ConnectError codes from @connectrpc/connect. Do not handle them as HTTP status codes.
  • Invalid arguments: Empty store or file names, invalid file path segments or characters, unsupported content types, or a destination that is not a Writable result in a TypeError. Invalid upload chunk sizes result in a RangeError. Non-binary source chunks are rejected during the transfer; the transport may wrap the underlying TypeError.

See the Binary Store API reference for runtime error semantics and the how-to guide for file-name restrictions and complete setup.