How to: Encrypt and decrypt data with the Dapr JavaScript SDK

Configure a cryptography component and encrypt and decrypt messages and files using the Dapr JavaScript SDK

Use DaprClient.crypto to encrypt and decrypt data without passing cryptographic keys to your application. This guide uses a local key for development, then shows how to work with buffers and files.

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 .mjs files without a TypeScript compiler. The same APIs are available in TypeScript.

Configure a local cryptography component

Create a keys directory and generate a random 256-bit symmetric key:

node -e "const fs = require('node:fs'); fs.mkdirSync('keys', { recursive: true }); fs.writeFileSync(require('node:path').join('keys', 'symmetric256'), require('node:crypto').randomBytes(32));"

Run this command only for initial setup. Rerunning it replaces the key, preventing you from decrypting data encrypted with the previous key unless you retained a copy.

Create a separate components directory and save the following as components\crypto.yaml:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: crypto-local
spec:
  type: crypto.dapr.localstorage
  version: v1
  metadata:
    - name: path
      value: '.\keys'

The path is resolved from the sidecar’s working directory. Run the commands below from your application directory; for other deployments, set the path to a directory accessible to the sidecar.

The component name, crypto-local, is the componentName used by the client. The key name, symmetric256, is the file name in the key directory. This example pairs a 256-bit symmetric key with A256KW key wrapping.

Encrypt and decrypt a message

Save the following as app.mjs:

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

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

try {
  const plaintext = Buffer.from("A message to encrypt", "utf8");
  const ciphertext = await client.crypto.encrypt(plaintext, {
    componentName: "crypto-local",
    keyName: "symmetric256",
    keyWrapAlgorithm: "A256KW",
  });

  const decrypted = await client.crypto.decrypt(ciphertext, {
    componentName: "crypto-local",
  });

  if (!decrypted.equals(plaintext)) {
    throw new Error("Decrypted bytes do not match the original message");
  }
  console.log("Message round-trip succeeded");
} finally {
  await client.stop();
}

Start the application with a sidecar that loads the component:

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

Passing data and options returns a Promise<Buffer> for both encryption and decryption. Encryption also accepts a string, encoded as UTF-8. Decryption uses the key reference embedded in the encrypted document by default, so this example does not need to supply keyName again.

To run the same example over gRPC, change communicationProtocol to CommunicationProtocolEnum.GRPC and daprPort to "50001".

Encrypt and decrypt files with streams

For files, pass only the options object to obtain a Node.js Duplex. Write plaintext into an encryption stream and read ciphertext from it; a decryption stream accepts ciphertext and emits plaintext.

Create a nonempty plaintext.txt in your application directory and save this example as files.mjs. It uses gRPC, the recommended protocol for large payloads and production streaming:

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

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

try {
  await pipeline(
    createReadStream("plaintext.txt"),
    await client.crypto.encrypt({
      componentName: "crypto-local",
      keyName: "symmetric256",
      keyWrapAlgorithm: "A256KW",
    }),
    createWriteStream("ciphertext.out"),
  );

  await pipeline(
    createReadStream("ciphertext.out"),
    await client.crypto.decrypt({ componentName: "crypto-local" }),
    createWriteStream("plaintext.out"),
  );

  console.log("Encrypted ciphertext.out and decrypted plaintext.out");
} finally {
  await client.stop();
}

Run it with the same component and ports:

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

Await each pipeline so failures propagate and encryption finishes before decryption starts. Use new streams for each operation. Do not combine encryption and decryption in one pipeline on runtimes affected by the concurrent cryptography limitation.

The stream overloads also work over HTTP, but the runtime buffers HTTP output until the entire input has arrived. A stream-shaped API does not remove that runtime memory cost.

Next steps

Read the client usage guidance for choosing ciphers, controlling embedded key references, preserving binary ciphertext, and handling errors. See the cryptography quickstart for additional examples.