Apps Artificial Intelligence CSS DevOps Go JavaScript Laravel Linux MongoDB MySQL PHP Python Rust Svelte Vue

Building a Nested Category API in Laravel 11: A Practical Guide

2 min read .
Building a Nested Category API in Laravel 11: A Practical Guide

Nested categories are common in e-commerce platforms, CMS applications, documentation systems, and other products that need hierarchical navigation. In Laravel, a self-referencing Eloquent relationship can model a category that belongs to a parent and has any number of children.

This Laravel 11 example builds a small API for creating and reading category trees.

1. Create the Laravel Project

composer create-project --prefer-dist laravel/laravel laravel-nested-categories
cd laravel-nested-categories
php artisan serve

2. Create the Category Model and Migration

php artisan make:model Category -m

Define the table in the generated migration:

Schema::create('categories', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->foreignId('parent_id')
        ->nullable()
        ->constrained('categories')
        ->cascadeOnDelete();
    $table->timestamps();
});

Run the migration:

php artisan migrate

cascadeOnDelete() means deleting a category also deletes its descendants through the database foreign key. Decide whether that behavior is appropriate for your application before using it in production.

3. Define Parent and Child Relationships

In app/Models/Category.php:

class Category extends Model
{
    use HasFactory;

    protected $fillable = ['name', 'parent_id'];

    public function parent()
    {
        return $this->belongsTo(Category::class, 'parent_id');
    }

    public function children()
    {
        return $this->hasMany(Category::class, 'parent_id');
    }

    public function childrenRecursive()
    {
        return $this->children()->with('childrenRecursive');
    }
}

The regular children() relationship loads one level. childrenRecursive() recursively eager-loads descendants, which is useful when the API needs to return a complete tree.

4. Create the Controller

Generate a controller:

php artisan make:controller CategoryController

Then add endpoints for reading and creating trees:

use App\Models\Category;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class CategoryController extends Controller
{
    public function index()
    {
        $categories = Category::query()
            ->whereNull('parent_id')
            ->with('childrenRecursive')
            ->get();

        return response()->json($categories);
    }

    public function store(Request $request)
    {
        $data = $request->validate([
            'name' => ['required', 'string', 'max:255'],
            'children' => ['sometimes', 'array'],
            'children.*.name' => ['required', 'string', 'max:255'],
            'children.*.children' => ['sometimes', 'array'],
        ]);

        $category = DB::transaction(function () use ($data) {
            return $this->createCategory($data);
        });

        return response()->json([
            'category' => $category->load('childrenRecursive'),
        ], 201);
    }

    private function createCategory(array $data, ?int $parentId = null): Category
    {
        $category = Category::create([
            'name' => $data['name'],
            'parent_id' => $parentId,
        ]);

        foreach ($data['children'] ?? [] as $child) {
            $this->createCategory($child, $category->id);
        }

        return $category;
    }
}

The transaction ensures that a failure while creating one descendant rolls back the entire tree instead of leaving a partially created hierarchy.

For arbitrarily deep input, consider recursive validation or a dedicated request object rather than validating only a few explicitly named levels.

5. Define API Routes

In Laravel 11 projects where API routes have been installed/enabled, add these routes to routes/api.php:

use App\Http\Controllers\CategoryController;
use Illuminate\Support\Facades\Route;

Route::get('/categories', [CategoryController::class, 'index']);
Route::post('/categories', [CategoryController::class, 'store']);

6. Test the API

Create a nested category tree:

curl -X POST http://localhost:8000/api/categories \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Electronics",
    "children": [
      {
        "name": "Mobile Phones",
        "children": [
          {"name": "Smartphones"},
          {"name": "Feature Phones"}
        ]
      },
      {"name": "Laptops"}
    ]
  }'

Fetch root categories and their descendants:

curl http://localhost:8000/api/categories

Conclusion

A self-referencing Eloquent relationship is a straightforward way to model hierarchical categories. Recursive eager loading and recursive creation work well for modest trees, while very deep or extremely large hierarchies may need stricter depth limits, different query strategies, or a more specialized tree representation.

Related Posts

chevron-up