I want to get a list of nodes and return them as JSON along with their tags (names included). After a lot of research, I've only come up with this solution:

I have an array of Nodes (\Drupal\Core\Entity\EntityInterface) loaded with Node::loadMultiple().

Iterating over the array of nodes, and getting their tags using $node->field_tags (or whatever the machine name of the tags is) which yields an array of \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem

Getting all the terms of the node using Term::loadMultiple() and then iterating over them and getting the name using $term->name[0]->value.

The problem is that there's an extra database read for every node, and I guess that the underlying query for this read is a JOIN :|.

Question: Is there an efficient way to load the tag (term) names along with target_id, target_type, target_uuid, etc? If no, is the current loop on each node the way to go???

More Info:

  • I'm paginating the results
  • The client can set the number of results per page, but there's a 50 nodes per page limit


P.S.: I have two more types of tags so I have to do this two more times. Bright side is, the other two tags are 1to1.

  • 1
    You don't need Term::loadMultiple(), because you get the same result by applying referencedEntities() on the reference field. But this doesn't make a difference performance wise. For this the main tool in D8 is caching. Either in a cacheable response (add the cache tags of the nodes/terms and the node list tag to it) or if you have a lot of variations in the lists of nodes, then cache single nodes incl. the retrieved tags. – 4k4 Nov 26 '17 at 19:39

If you're already looping over an array of nodes, have you considered collecting the term IDs before loading them in one query? That should save a lot of the overhead of running multiple queries:

$map = [];
$tids = [];

// Collect IDs for each node:
/** @var \Drupal\node\Entity\NodeInterface[] $nodes */
foreach ($nodes as $nid => $node) {
  /** @var \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $item */
  foreach ($node->field_tags as $item) {
    $tid = $item->target_id;
    $map[$nid][$tid] = $tid;
    $tids[$tid] = $tid;

// Load terms and collect their labels:
$term_labels = [];
/** @var \Drupal\taxonomy\Entity\TermInterface $term */
foreach (Term::loadMultiple($tids) as $tid => $term) {
  $term_labels[$tid] = $term->label();

// Assign the term labels to each node.
foreach ($map as $nid => $node_tids) {
  foreach ($node_tids as $tid) {
    $map[$nid][$tid] = $term_labels[$tid];
  • The obvious thing is that this is far better than what I'm doing right now, so thanks. Although I can't help but wonder if it's possible to just load the name field along with the nodes. – Milad.Nozari Nov 26 '17 at 18:05
  • No, it is not. But those loaded terms will be cached after you load them the first time – Berdir Nov 27 '17 at 12:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.