Skip to main content

Build hierarchies and graphs

Use Database records to represent trees, directed acyclic graphs (DAGs), and typed networks. A node is a record. A connection is either a parent identifier on that record or a record in a dedicated connection table.

Nodes and connections are ordinary tables in your app, each with its own access policy. Read them through JavaScript, Python, REST, Refine, or TaruviBase Console. Create and change them through JavaScript, Python, REST, or Console.

See the capability at a glance​

What you want to buildDatabase modelExample
A single-parent treeA nullable parent_id field on each nodeCategories, folders, an organization chart
A many-to-many relationshipA junction table with one foreign key for each sideProject members, tags, course enrollment
A DAG with multiple parentsA connection table with one directed row per parent linkPackage dependencies, approval chains
A typed networkA connection table with a type fielddepends_on, blocks, mentors, owns
Connections with business dataFields on the connection recordRole, weight, priority, validity dates
A graph visualizationAuthorized node and connection queries shaped in the applicationDependency map, knowledge graph, network view

Choose the simplest model that expresses the application's rules. Use a parent field when every node has at most one parent. Use a connection table as soon as a node can have multiple parents, multiple connection types, or data that belongs to the connection itself.

Understand the building blocks​

Building blockPurpose
NodeThe application record being connected
ConnectionA record that names the source and target nodes
DirectionThe meaning of from_id → to_id
TypeA stable application term such as parent, depends_on, or member_of
AttributesFields such as role, weight, status, or valid_from stored on the connection
Traversal boundaryA maximum depth, page size, and visited-node set chosen by the application

Define direction in a sentence before creating data. For example, child_id → parent_id can mean “the source category belongs beneath the target category,” while service_id → dependency_id can mean “the source service depends on the target service.” Use that direction consistently in writes, queries, diagrams, and tests.

Build a parent-child tree​

A category hierarchy can use a nullable UUID field for its direct parent:

{
"fields": [
{
"name": "id",
"type": "string",
"format": "uuid",
"constraints": {"required": true}
},
{
"name": "name",
"type": "string",
"constraints": {"required": true}
},
{
"name": "parent_id",
"type": "string",
"format": "uuid"
}
],
"primaryKey": ["id"]
}

Root categories store null in parent_id. Every other category stores the ID of its direct parent. Validate the proposed parent in the same app, prevent self-parenting and cycles, and define how children move when a parent is retired.

Read direct children​

Filter the node table by parent_id. parentId, parent_id, and PARENT_ID refer to the same category UUID in the interface you choose.

await database
.from('categories')
.filters('parent_id', 'eq', parentId)
.sort([
{field: 'name', order: 'asc'},
{field: 'id', order: 'asc'},
])
.page(1)
.pageSize(50)
.execute();

Traverse a tree safely​

Read one bounded level at a time:

  1. Start with the root category ID.
  2. Query records whose parent_id matches an ID in the current level.
  3. Sort by a stable field and id, then request an explicit page size.
  4. Add every returned ID to a visited set.
  5. Continue with the next level until the requested depth or an empty level.

The visited set makes the traversal deterministic even when imported data contains a cycle. A maximum depth and a maximum node count keep large trees fast and predictable.

Use the same records to create a breadcrumb in the opposite direction: read the current node, follow its parent_id, and stop at a root or the chosen depth.

Build a typed connection table​

Use a dedicated table when connections are many-to-many, multi-parent, or carry their own data. For a network of services, a service_connections table can contain:

FieldMeaning
idStable UUID for the connection record
from_idForeign key to the source service
to_idForeign key to the target service
typeApplication-defined connection such as depends_on or blocks
weightOptional numeric strength, cost, or priority
statusOptional lifecycle state for the connection
created_atWhen the connection was recorded

Treat the connection table like any other app-scoped table: create records through the record workflow, query them through the query workflow, and use foreign keys to keep source and target identifiers valid.

For undirected relationships, choose one canonical ordering for the two node IDs and query both from_id and to_id when reading a node's neighborhood. For directed relationships, preserve the declared source-to-target meaning.

Read a connected subgraph​

Build a bounded subgraph from explicit queries:

  1. Read the root node from the node table.
  2. Query the connection table by from_id, to_id, or both.
  3. Filter by type when the application needs one class of connection.
  4. Collect the connected node IDs and fetch those nodes with an in filter.
  5. Repeat only for the requested depth, skipping IDs already in the visited set.

This pattern supports outgoing dependencies, incoming dependents, a node's complete neighborhood, and multi-level traversals. Apply pagination and stable ordering independently to node and connection queries.

The query guide provides the filter, logical group, sorting, and pagination syntax. The relationship guide shows how to read each participating table through an explicit request.

Shape the result for the application​

The stored node and connection records can drive several application views:

  • For a tree, group records by parent_id and attach each group to its parent.
  • For a network view, map node records to visual nodes and connection records to visual links.
  • For a breadcrumb, follow one parent or outgoing connection at a time.
  • For a dependency path, retain the predecessor of every visited node and reconstruct the path when the target is reached.

Let TaruviBase check access on each read, and keep the display logic in your application. Each table is then read with its own request, page, and access check.

Production checklist​

  • Generate stable UUIDs for nodes and connection records.
  • Use compatible field types for every source, target, and primary key.
  • Define direction and connection-type vocabulary before accepting writes.
  • Add uniqueness rules for connections that must occur only once.
  • Validate cycles when the model requires a tree or DAG.
  • Bound page size, traversal depth, and the total number of visited nodes.
  • Test roots, leaves, multiple parents, disconnected nodes, cycles, and concurrent changes.
  • Authorize every node-table and connection-table operation with production roles.
  • Define deletion and retirement behavior for both nodes and connections.

Continue with relationships, advanced queries, or the interface map.