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 build | Database model | Example |
|---|---|---|
| A single-parent tree | A nullable parent_id field on each node | Categories, folders, an organization chart |
| A many-to-many relationship | A junction table with one foreign key for each side | Project members, tags, course enrollment |
| A DAG with multiple parents | A connection table with one directed row per parent link | Package dependencies, approval chains |
| A typed network | A connection table with a type field | depends_on, blocks, mentors, owns |
| Connections with business data | Fields on the connection record | Role, weight, priority, validity dates |
| A graph visualization | Authorized node and connection queries shaped in the application | Dependency 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 block | Purpose |
|---|---|
| Node | The application record being connected |
| Connection | A record that names the source and target nodes |
| Direction | The meaning of from_id → to_id |
| Type | A stable application term such as parent, depends_on, or member_of |
| Attributes | Fields such as role, weight, status, or valid_from stored on the connection |
| Traversal boundary | A 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.
- JavaScript SDK
- Python SDK
- Refine
- REST API
- TaruviBase Console
await database
.from('categories')
.filters('parent_id', 'eq', parentId)
.sort([
{field: 'name', order: 'asc'},
{field: 'id', order: 'asc'},
])
.page(1)
.pageSize(50)
.execute();
(
client.database
.from_("categories")
.filter("parent_id", "eq", parent_id)
.order_by("name", "id")
.page(1)
.page_size(50)
.execute()
)
useList({
resource: 'categories',
filters: [{field: 'parent_id', operator: 'eq', value: parentId}],
sorters: [
{field: 'name', order: 'asc'},
{field: 'id', order: 'asc'},
],
pagination: {currentPage: 1, pageSize: 50, mode: 'server'},
});
/api/apps/$TARUVI_APP_SLUG/datatables/categories/data/curl -G "$TARUVI_SITE_URL/api/apps/$TARUVI_APP_SLUG/datatables/categories/data/" \
-H "Authorization: Api-Key $TARUVI_API_KEY" \
--data-urlencode "parent_id=$PARENT_ID" \
--data-urlencode "ordering=name,id" \
--data-urlencode "page=1" \
--data-urlencode "page_size=50"
200Returns the direct children in data and their complete matching count in total.
- Open
categories, then select Data and Filters. - Add
parent_idEquals the parent category UUID. - Under Sort, add
nameascending andidascending. - Select Apply and use the data-grid pagination controls for more rows.
Traverse a tree safely
Read one bounded level at a time:
- Start with the root category ID.
- Query records whose
parent_idmatches an ID in the current level. - Sort by a stable field and
id, then request an explicit page size. - Add every returned ID to a visited set.
- 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:
| Field | Meaning |
|---|---|
id | Stable UUID for the connection record |
from_id | Foreign key to the source service |
to_id | Foreign key to the target service |
type | Application-defined connection such as depends_on or blocks |
weight | Optional numeric strength, cost, or priority |
status | Optional lifecycle state for the connection |
created_at | When 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:
- Read the root node from the node table.
- Query the connection table by
from_id,to_id, or both. - Filter by
typewhen the application needs one class of connection. - Collect the connected node IDs and fetch those nodes with an
infilter. - 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_idand 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.