Rest Generic Class provides native support for hierarchical data structures like categories, organizational charts, menu trees, and comment threads. This guide shows you how to model, query, and display tree structures efficiently.
Enabling Hierarchy Support
To enable hierarchy features, define the HIERARCHY_FIELD_ID constant in your model:
use Ronu\RestGenericClass\Core\Models\ BaseModel ;
class Category extends BaseModel
{
protected $fillable = [ 'name' , 'description' , 'parent_id' , 'sort_order' ];
const MODEL = 'category' ;
const RELATIONS = [ 'parent' , 'children' ];
// Enable hierarchy support
const HIERARCHY_FIELD_ID = 'parent_id' ;
public function parent ()
{
return $this -> belongsTo ( Category :: class , 'parent_id' );
}
public function children ()
{
return $this -> hasMany ( Category :: class , 'parent_id' );
}
}
HIERARCHY_FIELD_ID must be the name of the self-referencing foreign key column (typically parent_id, manager_id, or role_id).
Hierarchy Helper Methods
Once HIERARCHY_FIELD_ID is defined, your model gains several helper methods:
$category = Category :: find ( 5 );
// Check if hierarchy is supported
$category -> hasHierarchyField (); // true
// Get the hierarchy field name
$category -> getHierarchyFieldId (); // 'parent_id'
// Check if this is a root node (parent_id is null)
$category -> isHierarchyRoot (); // false
// Get the parent
$parent = $category -> hierarchyParent ; // Category instance or null
// Get all children
$children = $category -> hierarchyChildren ; // Collection
// Get all ancestors (up to root)
$ancestors = $category -> getHierarchyAncestors (); // Collection
// Get all descendants (with optional max depth)
$descendants = $category -> getHierarchyDescendants ( maxDepth : 3 ); // Collection
List Endpoint Hierarchy Modes
The hierarchy parameter transforms flat lists into tree structures.
Mode: roots_only
Returns only root nodes (nodes with parent_id = null):
GET /api/v1/categories
Content-Type : application/json
{
"hierarchy" : {
"filter_mode" : "roots_only"
}
}
Response:
{
"data" : [
{
"id" : 1 ,
"name" : "Electronics" ,
"parent_id" : null
},
{
"id" : 2 ,
"name" : "Clothing" ,
"parent_id" : null
}
]
}
Mode: flat_all
Returns all nodes in a flat list (default SQL behavior):
GET /api/v1/categories
Content-Type : application/json
{
"hierarchy" : {
"filter_mode" : "flat_all"
}
}
Mode: with_descendants
Returns root nodes with nested children:
GET /api/v1/categories
Content-Type : application/json
{
"hierarchy" : {
"filter_mode" : "with_descendants" ,
"children_key" : "children" ,
"max_depth" : 3
}
}
Response:
{
"data" : [
{
"id" : 1 ,
"name" : "Electronics" ,
"parent_id" : null ,
"children" : [
{
"id" : 3 ,
"name" : "Computers" ,
"parent_id" : 1 ,
"children" : [
{
"id" : 5 ,
"name" : "Laptops" ,
"parent_id" : 3 ,
"children" : []
},
{
"id" : 6 ,
"name" : "Desktops" ,
"parent_id" : 3 ,
"children" : []
}
]
},
{
"id" : 4 ,
"name" : "Phones" ,
"parent_id" : 1 ,
"children" : []
}
]
}
]
}
Mode: with_ancestors
Returns nodes with their ancestor chain:
GET /api/v1/categories
Content-Type : application/json
{
"hierarchy" : {
"filter_mode" : "with_ancestors"
},
"oper" : {
"and" : [ "id|=|5" ]
}
}
Response shows the full path from root to the selected node:
{
"data" : [
{
"id" : 1 ,
"name" : "Electronics" ,
"parent_id" : null ,
"children" : [
{
"id" : 3 ,
"name" : "Computers" ,
"parent_id" : 1 ,
"children" : [
{
"id" : 5 ,
"name" : "Laptops" ,
"parent_id" : 3 ,
"children" : []
}
]
}
]
}
]
}
Show Endpoint Hierarchy Modes
The show endpoint supports hierarchy modes when fetching a single record.
Mode: node_only
Returns just the node with an empty children array:
GET /api/v1/categories/3
Content-Type : application/json
{
"hierarchy" : {
"mode" : "node_only"
}
}
Response:
{
"id" : 3 ,
"name" : "Computers" ,
"parent_id" : 1 ,
"children" : []
}
Mode: with_descendants (Default)
Returns the node with all its descendants:
GET /api/v1/categories/3
Content-Type : application/json
{
"hierarchy" : {
"mode" : "with_descendants" ,
"max_depth" : 2
}
}
Response:
{
"id" : 3 ,
"name" : "Computers" ,
"parent_id" : 1 ,
"children" : [
{
"id" : 5 ,
"name" : "Laptops" ,
"parent_id" : 3 ,
"children" : []
},
{
"id" : 6 ,
"name" : "Desktops" ,
"parent_id" : 3 ,
"children" : []
}
]
}
Mode: with_ancestors
Returns the ancestor chain from root to this node:
GET /api/v1/categories/5
Content-Type : application/json
{
"hierarchy" : {
"mode" : "with_ancestors"
}
}
Response:
{
"id" : 1 ,
"name" : "Electronics" ,
"parent_id" : null ,
"children" : [
{
"id" : 3 ,
"name" : "Computers" ,
"parent_id" : 1 ,
"children" : [
{
"id" : 5 ,
"name" : "Laptops" ,
"parent_id" : 3 ,
"children" : []
}
]
}
]
}
Mode: full_branch
Returns ancestors + node + descendants (the complete branch):
GET /api/v1/categories/3
Content-Type : application/json
{
"hierarchy" : {
"mode" : "full_branch"
}
}
Hierarchy Configuration Options
{
"hierarchy" : {
"filter_mode" : "with_descendants" , // List mode: roots_only, flat_all, with_descendants, with_ancestors
"mode" : "with_descendants" , // Show mode: node_only, with_descendants, with_ancestors, full_branch
"children_key" : "children" , // Name of the children array property
"max_depth" : 3 , // Maximum nesting depth (null = unlimited)
"include_empty_children" : true // Include empty children arrays for leaf nodes
}
}
Without max_depth, deep trees can cause timeouts. Always set max_depth for large hierarchies or use pagination.
Combining Hierarchy with Filters
Filter the tree before building the hierarchy:
GET /api/v1/categories
Content-Type : application/json
{
"hierarchy" : {
"filter_mode" : "with_descendants" ,
"max_depth" : 2
},
"oper" : {
"and" : [ "active|=|true" ]
}
}
This:
Filters categories where active = true
Builds a tree with only active categories
Limits depth to 2 levels
Paginate root nodes, not the entire tree:
GET /api/v1/categories
Content-Type : application/json
{
"hierarchy" : {
"filter_mode" : "with_descendants" ,
"max_depth" : 2
},
"pagination" : {
"page" : 1 ,
"pageSize" : 10
}
}
This returns 10 root categories, each with up to 2 levels of descendants.
Pagination applies to root nodes only . Descendants are loaded in full (up to max_depth).
Real-World Examples
E-commerce Category Tree
Build a navigation menu:
GET /api/v1/categories?select=["id","name","slug","icon"]
Content-Type : application/json
{
"hierarchy" : {
"filter_mode" : "with_descendants" ,
"children_key" : "subcategories" ,
"max_depth" : 2
},
"oper" : {
"and" : [ "visible_in_menu|=|true" ]
},
"orderby" : [{ "sort_order" : "asc" }]
}
Organizational Chart
Show company structure:
GET /api/v1/employees?select=["id","name","title","avatar_url","manager_id"]&relations=["department:id,name"]
Content-Type : application/json
{
"hierarchy" : {
"filter_mode" : "with_descendants" ,
"children_key" : "reports" ,
"max_depth" : 4
},
"oper" : {
"and" : [ "active|=|true" ]
}
}
Display threaded comments:
GET /api/v1/comments?select=["id","body","user_id","parent_id","created_at"]&relations=["user:id,name,avatar_url"]
Content-Type : application/json
{
"hierarchy" : {
"filter_mode" : "with_descendants" ,
"children_key" : "replies" ,
"max_depth" : 3
},
"oper" : {
"and" : [ "post_id|=|123" , "approved|=|true" ]
},
"orderby" : [{ "created_at" : "desc" }]
}
Breadcrumb Trail
Get the path from root to a specific node:
GET /api/v1/categories/15
Content-Type : application/json
{
"hierarchy" : {
"mode" : "with_ancestors"
}
}
Response provides the complete breadcrumb chain:
[
{ "id" : 1 , "name" : "Electronics" },
{ "id" : 3 , "name" : "Computers" },
{ "id" : 5 , "name" : "Laptops" },
{ "id" : 15 , "name" : "Gaming Laptops" }
]
Problem: N+1 Queries
Without optimization, loading a tree recursively triggers one query per node.
Solution: Optimized Loading
The package uses optimized queries:
// Bad: N+1 queries
$categories = Category :: where ( 'parent_id' , null ) -> get ();
foreach ( $categories as $cat ) {
$cat -> load ( 'children' ); // 1 query per category
}
// Good: 2 queries total
$categories = Category :: where ( 'parent_id' , null )
-> with ( 'children' )
-> get ();
The hierarchy mode uses breadth-first loading to minimize queries:
Load all root nodes (1 query)
Load all level-1 children (1 query)
Load all level-2 children (1 query)
Continue until max_depth
Total queries = max_depth + 1
For a tree with 1000 nodes and max_depth=3, this is 4 queries instead of 1000.
Indexing
Always index the hierarchy field:
Schema :: create ( 'categories' , function ( Blueprint $table ) {
$table -> id ();
$table -> string ( 'name' );
$table -> unsignedBigInteger ( 'parent_id' ) -> nullable ();
$table -> timestamps ();
// Index for hierarchy queries
$table -> index ( 'parent_id' );
});
Troubleshooting
Hierarchy Not Working
Symptom: 400 error “Model does not support hierarchical listing”
Cause: HIERARCHY_FIELD_ID is not defined
Solution:
const HIERARCHY_FIELD_ID = 'parent_id' ;
Timeout on Large Trees
Symptom: Request times out with large hierarchies
Causes:
No max_depth set
Very deep tree (100+ levels)
Missing index on hierarchy field
Solutions:
Set max_depth: "max_depth": 3
Use pagination on roots
Add database index on parent_id
Filter the dataset: "oper": {"and": ["active|=|true"]}
Circular References
Symptom: Infinite loop or stack overflow
Cause: Data has circular references (A → B → A)
Solution: Add database constraint to prevent circular references:
DB :: statement ( '
ALTER TABLE categories
ADD CONSTRAINT no_self_reference
CHECK (id != parent_id)
' );
Empty Children Arrays
Symptom: Leaf nodes don’t have children property
Cause: include_empty_children is false
Solution:
{
"hierarchy" : {
"include_empty_children" : true
}
}
Next Steps
Advanced Filtering Combine hierarchy with complex filters
Relation Loading Eager-load relations in hierarchical queries
Caching Cache tree structures for faster responses
API Reference Complete hierarchy API reference
Evidence
File: src/Core/Services/BaseService.php
Lines: 679-754, 828-925, 936-1031
Implements showHierarchy(), buildShowHierarchy(), loadDescendantsOptimized(), and loadAncestorsOptimized()
File: src/Core/Models/BaseModel.php
Lines: 52-55, 169-240
Defines HIERARCHY_FIELD_ID and helper methods (hasHierarchyField(), getHierarchyAncestors(), getHierarchyDescendants(), etc.)
File: documentacion/doc-en/03-usage/02-scenarios.md
Lines: 76-104
Shows hierarchy configuration example