Dapr TypeScript: A Comprehensive Guide

Dapr (Distributed Application Runtime) is an open - source, portable, event - driven runtime that makes it easier for developers to build resilient, stateless, and stateful microservices that run on the cloud and edge. TypeScript, on the other hand, is a superset of JavaScript that adds static typing to the language, making it more robust and maintainable, especially for large - scale applications. Combining Dapr with TypeScript allows developers to take advantage of the benefits of both technologies. With Dapr’s building blocks such as service invocation, state management, pub/sub, and more, and TypeScript’s strong typing, we can build more reliable and scalable distributed applications. In this blog, we will explore the fundamental concepts, usage methods, common practices, and best practices of using Dapr with TypeScript.

Table of Contents

  1. Fundamental Concepts of Dapr TypeScript
  2. Setting up a Dapr TypeScript Project
  3. Usage Methods
    • Service Invocation
    • State Management
    • Pub/Sub
  4. Common Practices
  5. Best Practices
  6. Conclusion
  7. References

Fundamental Concepts of Dapr TypeScript

Dapr Building Blocks

Dapr provides several building blocks that can be used in a TypeScript application:

  • Service Invocation: Enables one service to call another service in a reliable and secure way. It abstracts away the underlying network protocol and service discovery.
  • State Management: Allows applications to store and retrieve state in a consistent and durable manner. Dapr supports various state stores like Redis, Cosmos DB, etc.
  • Pub/Sub: Facilitates asynchronous communication between services through a publish - subscribe pattern. Services can publish messages to topics and subscribe to receive messages from topics.

TypeScript Integration

TypeScript provides type definitions for Dapr SDKs, which helps in catching type - related errors at compile - time. This makes the code more robust and easier to understand and maintain. For example, when making a service invocation or interacting with the state store, the TypeScript types ensure that the input and output data are in the correct format.

Setting up a Dapr TypeScript Project

  1. Install Dapr CLI: Follow the official Dapr documentation to install the Dapr CLI on your machine.
  2. Initialize a Node.js Project: Create a new directory for your project and run npm init -y to initialize a new Node.js project.
  3. Install Dapr TypeScript SDK: Run npm install @dapr/dapr to install the Dapr TypeScript SDK.
  4. Set up TypeScript: Install TypeScript and related dependencies by running npm install typescript @types/node --save - dev. Create a tsconfig.json file with the following basic configuration:
{
    "compilerOptions": {
        "target": "ES6",
        "module": "commonjs",
        "outDir": "./dist",
        "rootDir": "./src",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true
    }
}

Usage Methods

Service Invocation

The following is an example of making a service invocation from one TypeScript service to another:

Sender Service (src/sender.ts)

import { DaprClient, HttpMethod } from '@dapr/dapr';

const daprHost = '127.0.0.1';
const daprPort = '3500';

async function main() {
    const client = new DaprClient(daprHost, daprPort);
    const appId = 'receiver-service';
    const method = 'hello';
    const data = { message: 'Hello from sender' };

    const result = await client.invoker.invoke(appId, method, HttpMethod.POST, data);
    console.log('Response from receiver:', result);
}

main().catch((error) => {
    console.error('Error:', error);
    process.exit(1);
});

Receiver Service (src/receiver.ts)

import { DaprServer, HttpMethod } from '@dapr/dapr';

const daprHost = '127.0.0.1';
const daprPort = '3500';
const serverPort = '3000';

async function main() {
    const server = new DaprServer(daprHost, daprPort, serverPort);

    await server.binding.receive('hello', HttpMethod.POST, async (data) => {
        console.log('Received data:', data);
        return { message: 'Hello back from receiver' };
    });

    await server.start();
}

main().catch((error) => {
    console.error('Error:', error);
    process.exit(1);
});

State Management

The following is an example of storing and retrieving state using Dapr in a TypeScript application:

import { DaprClient } from '@dapr/dapr';

const daprHost = '127.0.0.1';
const daprPort = '3500';

async function main() {
    const client = new DaprClient(daprHost, daprPort);
    const stateStoreName = 'statestore';
    const key = 'myKey';
    const value = { data: 'Hello, Dapr State!' };

    // Save state
    await client.state.save(stateStoreName, [
        {
            key: key,
            value: value
        }
    ]);

    // Retrieve state
    const retrievedValue = await client.state.get(stateStoreName, key);
    console.log('Retrieved state:', retrievedValue);
}

main().catch((error) => {
    console.error('Error:', error);
    process.exit(1);
});

Pub/Sub

The following is an example of publishing and subscribing to a topic using Dapr in a TypeScript application:

Publisher (src/publisher.ts)

import { DaprClient } from '@dapr/dapr';

const daprHost = '127.0.0.1';
const daprPort = '3500';

async function main() {
    const client = new DaprClient(daprHost, daprPort);
    const pubSubName = 'pubsub';
    const topic = 'myTopic';
    const data = { message: 'Hello from publisher' };

    await client.pubsub.publish(pubSubName, topic, data);
    console.log('Message published');
}

main().catch((error) => {
    console.error('Error:', error);
    process.exit(1);
});

Subscriber (src/subscriber.ts)

import { DaprServer } from '@dapr/dapr';

const daprHost = '127.0.0.1';
const daprPort = '3500';
const serverPort = '3001';

async function main() {
    const server = new DaprServer(daprHost, daprPort, serverPort);

    await server.pubsub.subscribe('pubsub', 'myTopic', async (data) => {
        console.log('Received message:', data);
    });

    await server.start();
}

main().catch((error) => {
    console.error('Error:', error);
    process.exit(1);
});

Common Practices

  • Error Handling: Always handle errors properly when using Dapr building blocks. For example, when making a service invocation or interacting with the state store, use try - catch blocks to handle potential errors.
  • Logging: Implement proper logging in your application to track the flow of Dapr operations. This helps in debugging and monitoring the application.
  • Configuration Management: Use environment variables to manage configuration such as Dapr host, port, state store name, etc. This makes it easier to deploy the application in different environments.

Best Practices

  • Code Organization: Organize your code in a modular way. For example, create separate files for different Dapr building block operations like service invocation, state management, and pub/sub.
  • Testing: Write unit and integration tests for your Dapr TypeScript code. Use testing frameworks like Jest to test the functionality of your application.
  • Type Safety: Leverage TypeScript’s type system to ensure that the data passed between different parts of the application and Dapr building blocks is in the correct format.

Conclusion

Dapr TypeScript is a powerful combination for building distributed applications. By understanding the fundamental concepts, usage methods, common practices, and best practices, developers can build more reliable, scalable, and maintainable applications. The Dapr TypeScript SDK provides a convenient way to interact with Dapr building blocks, and TypeScript’s type system enhances the code quality. Whether you are building a small - scale microservices application or a large - scale distributed system, Dapr TypeScript can be a great choice.

References