Client
Introduction
The Dapr Client allows you to communicate with the Dapr Sidecar and get access to its client facing features such as Publishing Events, Invoking Output Bindings, State Management, Secret Management, and much more.
Pre-requisites
- Dapr CLI installed
- Initialized Dapr environment
- Latest LTS version of Node or greater
Installing and importing Dapr’s JS SDK
- Install the SDK with
npm
:
npm i @dapr/dapr --save
- Import the libraries:
import { DaprClient, DaprServer, HttpMethod, CommunicationProtocolEnum } from "@dapr/dapr";
const daprHost = "127.0.0.1"; // Dapr Sidecar Host
const daprPort = "3500"; // Dapr Sidecar Port of this Example Server
const serverHost = "127.0.0.1"; // App Host of this Example Server
const serverPort = "50051"; // App Port of this Example Server
// HTTP Example
const client = new DaprClient(daprHost, daprPort);
// GRPC Example
const client = new DaprClient(daprHost, daprPort, CommunicationProtocolEnum.GRPC);
Running
To run the examples, you can use two different protocols to interact with the Dapr sidecar: HTTP (default) or gRPC.
Using HTTP (default)
import { DaprClient } from "@dapr/dapr";
const client = new DaprClient(daprHost, daprPort);
# Using dapr run
dapr run --app-id example-sdk --app-protocol http -- npm run start
# or, using npm script
npm run start:dapr-http
Using gRPC
Since HTTP is the default, you will have to adapt the communication protocol to use gRPC. You can do this by passing an extra argument to the client or server constructor.
import { DaprClient, CommunicationProtocol } from "@dapr/dapr";
const client = new DaprClient(daprHost, daprPort, CommunicationProtocol.GRPC);
# Using dapr run
dapr run --app-id example-sdk --app-protocol grpc -- npm run start
# or, using npm script
npm run start:dapr-grpc
Proxying Requests
By proxying requests, we can utilize the unique capabilities that Dapr brings with its sidecar architecture such as service discovery, logging, etc., enabling us to instantly “upgrade” our gRPC services. This feature of gRPC proxying was demonstrated in community call 41.
Creating a Proxy
To perform gRPC proxying, simply create a proxy by calling the client.proxy.create()
method:
// As always, create a client to our dapr sidecar
// this client takes care of making sure the sidecar is started, that we can communicate, ...
const clientSidecar = new DaprClient(daprHost, daprPort, CommunicationProtocolEnum.GRPC);
// Create a Proxy that allows us to use our gRPC code
const clientProxy = await clientSidecar.proxy.create<GreeterClient>(GreeterClient);
We can now call the methods as defined in our GreeterClient
interface (which in this case is from the Hello World example)
Behind the Scenes (Technical Working)
- The gRPC service gets started in Dapr. We tell Dapr which port this gRPC server is running on through
--app-port
and give it a unique Dapr app ID with--app-id <APP_ID_HERE>
- We can now call the Dapr Sidecar through a client that will connect to the Sidecar
- Whilst calling the Dapr Sidecar, we provide a metadata key named
dapr-app-id
with the value of our gRPC server booted in Dapr (e.g.server
in our example) - Dapr will now forward the call to the gRPC server configured
Building blocks
The JavaScript Client SDK allows you to interface with all of the Dapr building blocks focusing on Client to Sidecar features.
Invocation API
Invoke a Service
import { DaprClient, HttpMethod } from "@dapr/dapr";
const daprHost = "127.0.0.1";
const daprPort = "3500";
async function start() {
const client = new DaprClient(daprHost, daprPort);
const serviceAppId = "my-app-id";
const serviceMethod = "say-hello";
// POST Request
const response = await client.invoker.invoke(serviceAppId, serviceMethod, HttpMethod.POST, { hello: "world" });
// POST Request with headers
const response = await client.invoker.invoke(
serviceAppId,
serviceMethod,
HttpMethod.POST,
{ hello: "world" },
{ headers: { "X-User-ID": "123" } },
);
// GET Request
const response = await client.invoker.invoke(serviceAppId, serviceMethod, HttpMethod.GET);
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
For a full guide on service invocation visit How-To: Invoke a service.
State Management API
Save, Get and Delete application state
import { DaprClient } from "@dapr/dapr";
const daprHost = "127.0.0.1";
const daprPort = "3500";
async function start() {
const client = new DaprClient(daprHost, daprPort);
const serviceStoreName = "my-state-store-name";
// Save State
const response = await client.state.save(serviceStoreName, [
{
key: "first-key-name",
value: "hello",
},
{
key: "second-key-name",
value: "world",
},
]);
// Get State
const response = await client.state.get(serviceStoreName, "first-key-name");
// Get Bulk State
const response = await client.state.getBulk(serviceStoreName, ["first-key-name", "second-key-name"]);
// State Transactions
await client.state.transaction(serviceStoreName, [
{
operation: "upsert",
request: {
key: "first-key-name",
value: "new-data",
},
},
{
operation: "delete",
request: {
key: "second-key-name",
},
},
]);
// Delete State
const response = await client.state.delete(serviceStoreName, "first-key-name");
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
For a full list of state operations visit How-To: Get & save state.
Query State API
import { DaprClient } from "@dapr/dapr";
async function start() {
const client = new DaprClient(daprHost, daprPort);
const res = await client.state.query("state-mongodb", {
filter: {
OR: [
{
EQ: { "person.org": "Dev Ops" },
},
{
AND: [
{
EQ: { "person.org": "Finance" },
},
{
IN: { state: ["CA", "WA"] },
},
],
},
],
},
sort: [
{
key: "state",
order: "DESC",
},
],
page: {
limit: 10,
},
});
console.log(res);
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
PubSub API
Publish messages
import { DaprClient } from "@dapr/dapr";
const daprHost = "127.0.0.1";
const daprPort = "3500";
async function start() {
const client = new DaprClient(daprHost, daprPort);
const pubSubName = "my-pubsub-name";
const topic = "topic-a";
const message = { hello: "world" };
// Publish Message to Topic
const response = await client.pubsub.publish(pubSubName, topic, message);
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
Bindings API
Invoke Output Binding
Output Bindings
import { DaprClient } from "@dapr/dapr";
const daprHost = "127.0.0.1";
const daprPort = "3500";
async function start() {
const client = new DaprClient(daprHost, daprPort);
const bindingName = "my-binding-name";
const bindingOperation = "create";
const message = { hello: "world" };
const response = await client.binding.send(bindingName, bindingOperation, message);
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
For a full guide on output bindings visit How-To: Use bindings.
Secret API
Retrieve secrets
import { DaprClient } from "@dapr/dapr";
const daprHost = "127.0.0.1";
const daprPort = "3500";
async function start() {
const client = new DaprClient(daprHost, daprPort);
const secretStoreName = "my-secret-store";
const secretKey = "secret-key";
// Retrieve a single secret from secret store
const response = await client.secret.get(secretStoreName, secretKey);
// Retrieve all secrets from secret store
const response = await client.secret.getBulk(secretStoreName);
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
For a full guide on secrets visit How-To: Retrieve secrets.
Configuration API
Get Configuration Keys
import { DaprClient } from "@dapr/dapr";
const daprHost = "127.0.0.1";
const daprAppId = "example-config";
async function start() {
const client = new DaprClient(daprHost, process.env.DAPR_HTTP_PORT);
const config = await client.configuration.get("config-store", ["key1", "key2"]);
console.log(config);
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
Distributed Lock API
Try Lock and Unlock APIs
import { CommunicationProtocolEnum, DaprClient } from "@dapr/dapr";
import { LockStatus } from "@dapr/dapr/types/lock/UnlockResponse";
const daprHost = "127.0.0.1";
const daprPortDefault = "3500";
async function start() {
const client = new DaprClient(daprHost, daprPort);
const storeName = "redislock";
const resourceId = "resourceId";
const lockOwner = "owner1";
let expiryInSeconds = 1000;
console.log(`Acquiring lock on ${storeName}, ${resourceId} as owner: ${lockOwner}`);
const tryLockResponse = await client.lock.tryLock(storeName, resourceId, lockOwner, expiryInSeconds);
console.log(tryLockResponse);
console.log(`Unlocking on ${storeName}, ${resourceId} as owner: ${lockOwner}`);
const unlockResponse = await client.lock.unlock(storeName, resourceId, lockOwner);
console.log("Unlock API response: " + getResponseStatus(unlockResponse.status));
}
function getResponseStatus(status: LockStatus) {
switch (status) {
case LockStatus.Success:
return "Success";
case LockStatus.LockDoesNotExist:
return "LockDoesNotExist";
case LockStatus.LockBelongsToOthers:
return "LockBelongsToOthers";
default:
return "InternalError";
}
}
start().catch((e) => {
console.error(e);
process.exit(1);
});
For a full guide on distributed locks visit How-To: Use Distributed Locks.