Getting Started with AstraeaDB

This vignette walks through every major operation supported by the AstraeaDB R package. Each section contains a self-contained code example that you can copy into an R session once you have a running AstraeaDB server.

Starting the Server

Before you can connect from R you need a running AstraeaDB instance. The easiest way to start one locally is from the AstraeaDB source tree:

cargo run -p astraea-cli -- serve

The default ports are 7687 (JSON/TCP), 7688 (gRPC), and 7689 (Arrow Flight).

Connecting from R

There are two ways to create a client and connect to the server.

Option A: Convenience Function

The simplest approach is the astraea_connect() helper, which creates a client and calls connect() in one step:

library(AstraeaDB)

client <- astraea_connect()
# Equivalent to: astraea_connect(host = "127.0.0.1", port = 7687L)

Option B: Explicit Construction

If you need more control (for example, to set an authentication token), you can construct and connect the client separately:

library(AstraeaDB)

client <- AstraeaClient$new(
  host       = "127.0.0.1",
  port       = 7687L,
  auth_token = "my-secret-token"
)
client$connect()

In either case, you can verify the connection with a health-check ping:

info <- client$ping()
info

Creating Nodes

A node requires at least one label (character vector) and a set of properties (named list). You may optionally provide an embedding (numeric vector) for vector similarity search.

# Basic node
alice_id <- client$create_node(
  labels     = c("Person"),
  properties = list(name = "Alice", age = 30, city = "San Francisco")
)

# Node with multiple labels
bob_id <- client$create_node(
  labels     = c("Person", "Engineer"),
  properties = list(name = "Bob", age = 25, city = "New York")
)

# Node with an embedding vector
carol_id <- client$create_node(
  labels     = c("Person"),
  properties = list(name = "Carol", age = 35),
  embedding  = c(0.12, 0.87, 0.45, 0.33)
)

create_node() returns the integer ID assigned to the new node by the server.

Reading Nodes

Retrieve a node by its ID. The result is a list with labels and properties:

node <- client$get_node(alice_id)

node$labels
#> [1] "Person"

node$properties$name
#> [1] "Alice"

node$properties$age
#> [1] 30

Updating Nodes

update_node() uses merge semantics: existing properties that are not mentioned in the update are preserved. New properties are added and existing ones are overwritten.

# Add a new property; existing name, age, city are kept
client$update_node(alice_id, list(department = "Research"))

# Verify
updated <- client$get_node(alice_id)
updated$properties$department
#> [1] "Research"

updated$properties$city
#> [1] "San Francisco"

Deleting Nodes

Deleting a node also removes all edges connected to it:

temp_id <- client$create_node(
  labels     = c("Temp"),
  properties = list(note = "will be deleted")
)
client$delete_node(temp_id)

Creating Edges

An edge connects a source node to a target node with a typed relationship. Optional parameters include properties, weight, and temporal validity bounds.

# Simple edge
edge1 <- client$create_edge(
  source    = alice_id,
  target    = bob_id,
  edge_type = "KNOWS",
  properties = list(context = "work")
)

# Weighted edge
edge2 <- client$create_edge(
  source    = bob_id,
  target    = carol_id,
  edge_type = "FOLLOWS",
  weight    = 0.7
)

# Temporal edge with validity window (milliseconds since epoch)
# Valid from 2023-01-01 to 2024-01-01
edge3 <- client$create_edge(
  source     = alice_id,
  target     = carol_id,
  edge_type  = "MENTORS",
  properties = list(topic = "graph databases"),
  weight     = 1.0,
  valid_from = 1672531200000,
  valid_to   = 1704067200000
)

create_edge() returns the integer ID of the new edge. The valid_from and valid_to parameters are numeric scalars representing milliseconds since the Unix epoch. When set, the edge is only visible in temporal queries whose timestamp falls within the window.

Reading and Updating Edges

# Read
edge <- client$get_edge(edge1)
edge$edge_type
#> [1] "KNOWS"
edge$properties$context
#> [1] "work"

# Update (merge semantics, same as nodes)
client$update_edge(edge1, list(strength = "strong"))

Deleting Edges

client$delete_edge(edge2)

Graph Traversals

Neighbors

Retrieve direct neighbors of a node. You can filter by direction ("outgoing", "incoming", or "both") and by edge type:

# Outgoing neighbors (default)
out <- client$neighbors(alice_id, direction = "outgoing")

# Incoming neighbors
inc <- client$neighbors(bob_id, direction = "incoming")

# Both directions, filtered by edge type
knows <- client$neighbors(alice_id, direction = "both", edge_type = "KNOWS")

Each entry in the result contains at least node_id and edge_id.

Breadth-First Search (BFS)

BFS explores the graph outward from a starting node up to a maximum depth:

bfs_result <- client$bfs(alice_id, max_depth = 2L)

# Each entry has node_id and depth
for (entry in bfs_result) {
  cat(sprintf("Node %d at depth %d\n", entry$node_id, entry$depth))
}

Shortest Path

Find the shortest path between two nodes. Set weighted = TRUE to use edge weights (Dijkstra’s algorithm) instead of hop count:

# Unweighted (hop count)
sp <- client$shortest_path(alice_id, carol_id)
sp$path    # integer vector of node IDs
sp$length  # number of hops

# Weighted (minimizes total edge weight)
sp_w <- client$shortest_path(alice_id, carol_id, weighted = TRUE)
sp_w$path
sp_w$cost  # total weight

GQL Queries

AstraeaDB supports a subset of Graph Query Language (GQL). You can run queries with MATCH, WHERE, RETURN, ORDER BY, and LIMIT clauses:

# Find all Person nodes
result <- client$query("MATCH (p:Person) RETURN p.name, p.age")

# Filter with WHERE
result <- client$query(
  "MATCH (p:Person) WHERE p.age > 25 RETURN p.name, p.city"
)

# Edges in the pattern
result <- client$query(
  "MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a.name, b.name"
)

# Ordering and limiting
result <- client$query(
  "MATCH (p:Person) RETURN p.name, p.age ORDER BY p.age LIMIT 10"
)

The query() method returns the raw server response data. For tabular results you may want to use the UnifiedClient$query_df() method (see the Advanced Features vignette for Arrow Flight support).

Batch Operations

When you need to create or delete many nodes or edges at once, batch methods are more convenient than calling single-item methods in a loop.

Batch Create

# Create multiple nodes
node_ids <- client$create_nodes(list(
  list(labels = c("City"), properties = list(name = "San Francisco", state = "CA")),
  list(labels = c("City"), properties = list(name = "New York", state = "NY")),
  list(labels = c("City"), properties = list(name = "Austin", state = "TX"))
))

# Create multiple edges
edge_ids <- client$create_edges(list(
  list(source = alice_id, target = node_ids[1], edge_type = "LIVES_IN"),
  list(source = bob_id,   target = node_ids[2], edge_type = "LIVES_IN"),
  list(source = carol_id, target = node_ids[3], edge_type = "LIVES_IN",
       weight = 0.8)
))

Batch Delete

# Delete multiple nodes (also removes their edges)
deleted_count <- client$delete_nodes(node_ids)
cat(deleted_count, "nodes deleted\n")

# Delete multiple edges
deleted_edges <- client$delete_edges(edge_ids)

Batch delete operations silently skip any IDs that do not exist or have already been deleted, and return the count of successful deletions.

Data Frame Integration

The package provides import and export helpers that bridge between R data frames and the AstraeaDB graph.

Importing Nodes from a Data Frame

people_df <- data.frame(
  label = c("Person", "Person", "Person"),
  name  = c("Dave", "Eve", "Frank"),
  age   = c(40, 28, 55),
  city  = c("Chicago", "Boston", "Denver"),
  stringsAsFactors = FALSE
)

new_ids <- client$import_nodes_df(people_df, label_col = "label")
new_ids
#> [1] 10 11 12

The label_col parameter identifies which column holds the node label. All other columns become node properties. You can also specify embedding_cols if some columns should be assembled into an embedding vector:

embed_df <- data.frame(
  label = c("Document", "Document"),
  title = c("Doc A", "Doc B"),
  e1    = c(0.1, 0.4),
  e2    = c(0.9, 0.5),
  e3    = c(0.3, 0.8),
  stringsAsFactors = FALSE
)

doc_ids <- client$import_nodes_df(
  embed_df,
  label_col      = "label",
  embedding_cols = c("e1", "e2", "e3")
)

Importing Edges from a Data Frame

edges_df <- data.frame(
  source = new_ids[c(1, 2)],
  target = new_ids[c(2, 3)],
  type   = c("KNOWS", "MENTORS"),
  since  = c(2018, 2021),
  stringsAsFactors = FALSE
)

edge_ids <- client$import_edges_df(
  edges_df,
  source_col = "source",
  target_col = "target",
  type_col   = "type"
)

Additional column-mapping parameters are available: weight_col, valid_from_col, and valid_to_col. All columns not mapped to a structural role become edge properties.

Exporting Nodes to a Data Frame

df <- client$export_nodes_df(new_ids)
df
#>   node_id labels   name age    city
#> 1      10 Person   Dave  40 Chicago
#> 2      11 Person    Eve  28  Boston
#> 3      12 Person  Frank  55  Denver

The exported data frame contains a node_id column, a comma-separated labels column, and one column per property. Nodes with different property sets are aligned; missing properties are filled with NA.

Exporting BFS Results to a Data Frame

bfs_df <- client$export_bfs_df(alice_id, max_depth = 2L)
bfs_df
#>   node_id depth labels   name age          city
#> 1       1     0 Person  Alice  30 San Francisco
#> 2       2     1 Person    Bob  25      New York
#> 3       3     2 Person  Carol  35          <NA>

This runs a BFS internally, then fetches each visited node and flattens the results into a data frame with an additional depth column.

Disconnecting

Always disconnect when you are done. This closes the underlying TCP socket:

client$disconnect()

A good pattern for scripts is to use on.exit() to guarantee cleanup:

client <- astraea_connect()
on.exit(client$disconnect(), add = TRUE)

# ... do work ...

Error Handling

All client methods signal R errors when the server returns an error response or the connection fails. Use tryCatch() to handle errors gracefully:

tryCatch(
  {
    node <- client$get_node(999999L)
  },
  error = function(e) {
    message("Operation failed: ", conditionMessage(e))
  }
)

Typical error scenarios include:

You can check whether the server is reachable before attempting to connect:

if (astraea_server_available()) {
  client <- astraea_connect()
  # ... work ...
  client$disconnect()
} else {
  message("AstraeaDB server is not available.")
}

Summary

This vignette covered the core workflow for interacting with AstraeaDB from R:

  1. Start the server and connect from R.
  2. Create, read, update, and delete nodes and edges.
  3. Traverse the graph with neighbors, BFS, and shortest-path queries.
  4. Run GQL queries for declarative pattern matching.
  5. Use batch and data-frame helpers for bulk operations.
  6. Handle errors and clean up connections.

For vector search, temporal queries, GraphRAG, and Arrow Flight transport, see vignette("advanced-features").