First you have to separate the nodes subordination from the nodes itself. You need a pivot table `trees` having exactly two columns: `nodeID -- parentID`. The node having `parentID` equal to NULL is a root node for certain tree. Pivot table `trees` can contain more than one tree in it. This table requires a primary key (nodeID) and an unique multicolumn key (nodeID, parentID) - each node should be listed once and each node can have only one parent. An additional restriction - node can not refer to itself as a parent.

    CREATE TABLE trees (
      nodeID INT UNSIGNED NOT NULL,
      parentID INT UNSIGNED, -- nullable for root nodes
      PRIMARY KEY ( nodeID ), -- each node is listed once
      UNIQUE KEY  ( nodeID, parentID ), -- no duplicates
      CONSTRAINT treeselfref CHECK ( nodeID <> parentID ) -- can't be parent for itself
    )

Then you need a set of procedures for nodes manipulations:

    AddNode( nodeID, parentID )
    DeleteNode( nodeID ) -- you have to manage orphan childs
    DeleteBranch( nodeID ) -- and all its childs/subchilds
    GetParent( nodeID )  -- for the given node
    GetChilds( parentID ) -- whos parent is parentID
    GetBranch( parentID ) -- childs and their childs up to the leaves, ranked
    GetLeaves( nodeID ) -- all leaves attached to the node
    GetPath( nodeID ) -- full subordination path from the root up to the node, ranked
    MergeBranches( nodeIDa, nodeIDb )
    MoveBranch( nodeID, newParentID )
    . . . . . 

An exact set of procedures and their logic depend on the items/categories nature. So here is the sample code for the `GetLeaves( nodeID )` procedure, just for illustration of the basic principle:

    WITH RECURSIVE cte (nodeID, parentID) AS 
    ( SELECT w.*   
        FROM trees AS w 
       WHERE w.nodeID = _nodeID  -- seed node for branch 
      UNION ALL
      SELECT q.*
        FROM cte   AS z
        JOIN trees AS q ON q.parentID = z.nodeID  -- childs, recurrent
    ) SELECT s.nodeID
        FROM cte        AS s
        LEFT JOIN trees AS r ON r.parentID = s.nodeID
      WHERE r.nodeID IS NULL; -- nodes not listed as parents for other nodes i.e. leaves

Here is the code for `GetPath( nodeID )` procedure that returns a list of all parent nodes from the tree root up to the given node, with rank (distance):

    WITH RECURSIVE cte (rank, nodeID, parentID) AS 
    ( SELECT 0 AS rank, w.*
        FROM trees AS w 
       WHERE w.nodeID = _nodeID
      UNION ALL
      SELECT z.rank+1, q.*
        FROM cte   AS z
        JOIN trees AS q  ON q.nodeID = z.parentID
    ) SELECT s.*, f.name 
        FROM cte        AS s
        JOIN nodes      AS n ON n.ID = s.nodeID
        JOIN categories AS f ON f.ID = n.categoryID
       ORDER BY s.rank DESC
When invoked for nodeID = 123 it will return something like that:

    +----+------+--------+----------+
    |rank|nodeID|parentID|name      |
    +----+------+--------+----------+
    |  5 | 1    | NULL   | root     |
    |  4 | 7    | 1      | edible   |
    |  3 | 11   | 7      | fruit    |
    |  2 | 31   | 11     | domestic |
    |  1 | 42   | 31     | apple    |
    |  0 | 123  | 42     | mcintosh |
    +----+------+--------+----------+