02 / How Lupus works

CE API and JSON:API

JSON:API answers “what data does this resource contain”. CE API answers “what supported presentation does Drupal deliver for this route”. The difference is where composition is decided, not that one API is universally better.

All walkthroughs ↗

With JSON:API

Query resources by type and UUID with attributes, relationships, filters, includes and resource pagination. The frontend owns queries, composition and views. Useful for applications, multiple channels and data unrelated to a page. It does not by default execute our View display or define a visual system.

With /ce-api

Request /ce-api/demo/insights?page=1. Drupal resolves the route, checks access, executes the View and delivers teaser elements and a pager. Next.js does not recreate that View’s publication or language filters; it presents the received contract.

Less duplicated logic, not zero frontend

The frontend still implements components, navigation, pager links, errors and accessibility. CE does not send React code or convert Drupal libraries into React. A new element needs a supported renderer or component.

Not a universal write API

The page API does not replace CRUD operations, specialised searches or business services. CE pages can coexist with JSON:API or other APIs for data and interactions. JSON:API is not enabled in this POC: its URL example is illustrative, not an endpoint exercised here.

Real example from this installation

Real second-page View response: rows contains teasers; props.pager preserves totalPages and current. The contrasting illustrative JSON:API request would be GET /jsonapi/node/ntt_insight/{uuid}.

Inspect the contract received on the server
{
  "title": "Ideas connecting technology and experience",
  "content_format": "json",
  "content": {
    "element": "ntt-insights",
    "props": {
      "title": "Ideas connecting technology and experience",
      "viewId": "ntt_demo_insights",
      "displayId": "page_1",
      "rowsWrapper": "",
      "pager": {
        "totalPages": 3,
        "current": 1
      }
    },
    "slots": {
      "rows": [
        {
          "element": "ntt-insight-card",
          "props": {
            "href": "/en/demo/insights/drupal-views",
            "title": "Views: reuse the query and pager"
          },
          "slots": {
            "body": "Drupal selects content; the frontend does not duplicate the View filters."
          }
        },
        {
          "element": "ntt-insight-card",
          "props": {
            "href": "/en/demo/insights/design-system",
            "title": "Blocks and modules: Drupal outside Canvas"
          },
          "slots": {
            "body": "A module route and a configurable block arrive through CE API and render with React."
          }
        },
        {
          "element": "ntt-insight-card",
          "props": {
            "href": "/en/demo/insights/delivery-contract",
            "title": "CE API: from route to HTML"
          },
          "slots": {
            "body": "A presentation API complementing data APIs and simplifying a recursive renderer."
          }
        }
      ]
    }
  },
  "metatags": {
    "meta": [],
    "link": []
  },
  "blocks": {
    "sidebar_first": {
      "canvas_lupus_theme_page_title": {
        "element": "drupal-markup",
        "slots": {
          "default": "\n\n"
        }
      },
      "canvas_lupus_theme_powered": {
        "element": "drupal-markup",
        "slots": {
          "default": "<span>Powered by <a href=\"https://www.drupal.org\">Drupal</a></span>"
        }
      }
    },
    "header": {
      "canvas_lupus_theme_site_branding": {
        "element": "drupal-markup",
        "slots": {
          "default": "<img src=\"https://dev-headless-lupus.pantheonsite.io/themes/custom/canvas_lupus_theme/logo.svg\" alt=\"Home\" />\nCanvas Lupus POC"
        }
      }
    },
    "primary_menu": {
      "canvas_lupus_theme_main_menu": {
        "element": "drupal-markup",
        "slots": {
          "default": "\n              <ul>\n              <li>\n        <a href=\"/en\" data-drupal-link-system-path=\"&lt;front&gt;\">Home</a>\n              </li>\n        </ul>\n  \n\n"
        }
      }
    },
    "secondary_menu": {
      "canvas_lupus_theme_account_menu": {
        "element": "drupal-markup",
        "slots": {
          "default": "\n              <ul>\n              <li>\n        <a href=\"/en/user/login\" data-drupal-link-system-path=\"user/login\">Log in</a>\n              </li>\n        </ul>\n  \n\n"
        }
      }
    },
    "highlighted": {
      "canvas_lupus_theme_messages": {
        "element": "drupal-markup",
        "slots": {
          "default": "<div data-drupal-messages-fallback class=\"hidden\"></div>"
        }
      }
    },
    "breadcrumb": {
      "canvas_lupus_theme_breadcrumbs": {
        "element": "drupal-markup",
        "slots": {
          "default": "  <nav aria-labelledby=\"system-breadcrumb\">\n    <h2 id=\"system-breadcrumb\" class=\"visually-hidden\">Breadcrumb</h2>\n    <ol>\n          <li>\n                  <a href=\"/en\">Home</a>\n              </li>\n        </ol>\n  </nav>\n"
        }
      }
    }
  }
}

Architecture sources