Quickstart

This guide will help you get started with Graph Compose. We'll walk through installing the SDK, setting up authentication, and creating your first workflow. By the end, you'll have a working example of HTTP-based workflow orchestration using our intuitive builder pattern.

Installation

First, install the Graph Compose client package using your preferred package manager:

npm install @graph-compose/client

Creating your first workflow

Let's create a workflow that processes an order. This example demonstrates key concepts like the builder pattern, handlebars expressions with JSONata, and dynamic URL parameters.

import { GraphCompose } from '@graph-compose/client'

// Initialize the client with your API token
const graph = new GraphCompose({ 
  token: process.env.GRAPH_COMPOSE_TOKEN  // Get this from your dashboard
})

// Build your workflow using the intuitive builder pattern
const workflow = graph
  // Fetch order details
  .node("get_order")
    .get("https://your-api-url.com/api/orders/{{ context.orderId }}")
    .withRetries({
      maximumAttempts: 3,
      initialInterval: "1s"
    })
  .end()
  
  // Check inventory for all items
  .node("check_inventory")
    .post("https://your-api-url.com/api/inventory/check")
    .withBody({
      items: "{{ results.get_order.items }}"  // Access previous node's result
    })
    .withDependencies(["get_order"])
  .end()
  
  // Reserve inventory with error boundary protection
  .errorBoundary("inventory_rollback", ["reserve_inventory"])
    .post("https://your-api-url.com/api/inventory/release")
    .withBody({
      items: "{{ results.get_order.items }}"
    })
  .end()
  
  .node("reserve_inventory")
    .post("https://your-api-url.com/api/inventory/reserve")
    .withBody({
      // Use JSONata expressions inside handlebars
      items: "{{ $map(results.get_order.items, function($item) {
        $merge([$item, {'reserved': true}])
      }) }}"
    })
    .withDependencies(["check_inventory"])
  .end()
  
  // Process payment
  .node("process_payment")
    .post("https://your-api-url.com/api/payments")
    .withBody({
      orderId: "{{ results.get_order.id }}",
      amount: "{{ results.get_order.total }}",
      items: "{{ results.get_order.items }}"
    })
    .withDependencies(["reserve_inventory"])
    .withStartToCloseTimeout("30s")  // Extended timeout for payment processing
  .end();

// Execute the workflow asynchronously (recommended)
const { workflowId } = await workflow.execute({ // Renamed method, now async
  orderId: "123",  // This becomes available as context.orderId
  webhookUrl: "https://your-domain.com/webhook"  // Optional webhook for status updates
});

// Since it's async, we get the ID back immediately.
// We'd need to query status or use webhooks to know the final result.
console.log('Workflow execution started with ID:', workflowId);
// console.log('Status:', result.status); // Status is not returned directly from async execute

Understanding the workflow

Let's break down how the workflow above works, step by step:

1. Building the Workflow Graph

The workflow is defined as a directed acyclic graph (DAG) where each node represents an action, typically an HTTP request. The builder pattern makes defining this structure intuitive:

const workflow = graph
  .node("first_node")
  // ... node configuration
  .end()
  
  .node("second_node")
  // ... node configuration
  .end();
Learn more about Workflow Basics & DAGs

2. Node Dependencies

Nodes can depend on other nodes using withDependencies(). In our example:

  • check_inventory depends on get_order to access the order items
  • reserve_inventory depends on check_inventory to ensure items are available
  • process_payment depends on reserve_inventory to confirm the reservation

Graph Compose automatically handles the execution order based on these dependencies.

3. Data Flow with Handlebars

Data flows between nodes using handlebars syntax {{ }}. You can:

  • Access workflow inputs: {{ context.orderId }}
  • Use previous node results: {{ results.get_order.items }}
  • Transform data with simple expressions: {{ results.get_order.total }}
Learn more about Template Syntax

4. Configuring Durability and Resiliency

The workflow includes multiple safety mechanisms:

// Retry configuration for transient failures
.withRetries({
  maximumAttempts: 3,
  initialInterval: "1s"
})

// Error boundary for cleanup operations
.errorBoundary("inventory_rollback", ["reserve_inventory"])
  .post("https://your-api-url.com/api/inventory/release")
  // ... cleanup configuration
.end()

// Timeout for long-running operations
.withStartToCloseTimeout("30s")

5. Execution

Finally, execute the workflow asynchronously by providing the necessary context:

const { workflowId } = await workflow.execute({ // Use the default async execute method
  orderId: "123",
  webhookUrl: "https://your-domain.com/webhook"
});

console.log('Workflow started with ID:', workflowId);
// Need to check status via API or webhook later

The webhookUrl is optional and will receive status updates as the workflow progresses.

๐Ÿ’ก

Pro Tips

โ€ข Start simple: Build your workflow node by node, testing each step
โ€ข Use error boundaries around critical operations that need cleanup
โ€ข Keep JSONata expressions simple for better maintainability