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.
Before you can create workflows, you'll need an API token from your Graph Compose dashboard. You can find it under Settings ยป API Keys.
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.
The examples below show different aspects of Graph Compose:
- Builder Pattern: Shows a practical e-commerce workflow with error handling
- REST API: The same workflow in raw JSON format for non-TypeScript users
- Advanced Flow Control: Demonstrates polling, branching, and conditional termination
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();
2. Node Dependencies
Nodes can depend on other nodes using withDependencies()
. In our example:
check_inventory
depends onget_order
to access the order itemsreserve_inventory
depends oncheck_inventory
to ensure items are availableprocess_payment
depends onreserve_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 }}
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")
Optional Validation: Before executing, you can optionally validate your workflow definition client-side using workflow.validate()
. This helps catch structural errors early. See Core Concepts for details.
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