Guide 01 · Developer docs

Choosing how to build

Decide where the pages of a Core dna site are composed before writing code: on the platform, in your own application, or on the platform with a few pieces filled in by the browser. The decision tree, each shape in depth, multi-brand and languages, and a table and case studies to make the call.

L2
Unit 02 · Headless, over the three API surfaces

REST, GraphQL or a custom endpoint

The three ways a headless front end reaches the platform, each with a real request: REST for complete records and every write, GraphQL for one shaped read across record types, a synchronous job for an endpoint of your own. Then how to choose, by round trips and caching.

Who this is for. The developer of a headless front end, and of the JavaScript in a hybrid site. Teams tend to use REST for everything and then wonder why one screen needs a dozen requests. After this page you know what each surface is for and can pick one per request. Guide 6 covers each in full.

REST: the complete surface

Every module has its own prefix under /cdna-api (/cdna-api/pages, /cdna-api/blogs, /cdna-api/products and so on), and the same set of routes for each record type: a list, one record, create, edit and delete. It is the only surface that writes. A list takes limit, offset and orderBy, filters by field, and returns only the fields a properties selector names:

A list: the published children of a page, three fields each
GET /cdna-api/pages/pages?parent.id=72&limit=3&orderBy=position&retrieveUnpublished=0&properties={id,name,fullPath} HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Response 200 OK
{
  "result": "success",
  "totalCount": 2,
  "data": [
    {"id": 818, "name": "API Reference", "fullPath": "/developer-docs/api-reference"},
    {"id": 1878, "name": "Reference", "fullPath": "/developer-docs/reference"}
  ]
}
Real response from this documentation site

One page can be read by its id or by its path. The path form also resolves a page's custom URL, which makes it the routing call of a headless front end: the visitor's path goes in, the page comes out, or a 404 when there is none. It covers pages only; posts, products, book pages and custom entity records have routes and URL rules of their own.

One page, found by its path
GET /cdna-api/pages/page/developer-docs/reference?retrieveUnpublished=0&properties={id,name,fullPath,activeRevision{layout{name}}} HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Response
{
  "result": "success",
  "data": {
    "id": 1878,
    "name": "Reference",
    "fullPath": "/developer-docs/reference",
    "activeRevision": {"layout": {"name": "Hub Reference"}}
  }
}
{
  "result": "error",
  "message": "Page or record not found"
}
Real responses from this documentation site

GraphQL: one shaped read

A single endpoint, POST /cdna-api/graphql, takes a query that names several record types and the fields wanted from each, and answers them all in one response. It reads pages, blogs and posts, books, FAQs, products and their categories, menus, forms and their submissions, global components, custom entity records, users, rewrites and a few others. Separate the fields with commas: without them, the names run together and come back as an error in place of the value.

Two guides and a menu in one request
POST /cdna-api/graphql HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Content-Type: application/json

{
  "query": "query { guides: Book(filter: {id_in: [1590, 1599]}) { id, name, fullPath }, footer: Menu(filter: {name: \"Footer Menu\"}) { id, rootElements { name } } }"
}
Response
{
  "result": "success",
  "data": {
    "guides": [
      {"id": 1590, "name": "Choosing how to build", "fullPath": "/books/choosing-how-to-build"},
      {"id": 1599, "name": "Core dna for site builders", "fullPath": "/books/start-here"}
    ],
    "footer": [
      {"id": 2, "rootElements": [{"name": "Documentation"}, {"name": "Reference"}, {"name": "Help"}, {"name": "Core dna"}]}
    ]
  }
}
{
  "result": "error",
  "message": "Currently, only Query GraphQL requests are supported by this API"
}
Real responses from this documentation site

The limit is the one in that second response: GraphQL answers queries only, so every write goes through REST.

Custom endpoints: a synchronous job

A job (Orchestration in the admin; jobs were called hooks before the module was renamed) can be started by a web event, which makes it an HTTP endpoint you define in the admin, with no framework code: /cdna-api/webhook/{slug}, where anything after the slug is passed to the job. It answers the methods ticked on the web event, from GET, POST, PUT, DELETE and OPTIONS. What the caller gets back depends on the job:

  • Synchronous, with an HTTP response action: the status, headers and body that action builds. They are templates, so the body can read records and print JSON as a template would.
  • Synchronous, without one: a JSON summary of the job run, with 200 when it succeeded and 500 when it failed.
  • Not synchronous: the job run is queued, and the caller gets it back at once, before any action has run.

This site has one such endpoint, which files a JSON body as a new unpublished page under its API sandbox page and answers with the new page's id:

A synchronous job that creates a record and answers 201
POST /cdna-api/webhook/api-sandbox-add-record HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Content-Type: application/json

{
  "name": "Filed through a job",
  "externalId": "ticket-4021"
}
Response
{"id":1922,"name":"Filed through a job"}
{
  "result": "error",
  "message": "Access to the specified record has been denied. Please contact your administrator if you think this is a mistake."
}
Real responses from this documentation site, on its API sandbox

Two things to know before building one. A web event with Authentication required admits backend users only (an OAuth token, an API key or an admin's session), which is why the call without credentials above answers 403; without that setting it is open to anyone who has the URL. And a synchronous job reads unpublished records as well as published ones, so a response that lists records must check publication itself. Custom endpoints with synchronous jobs builds this endpoint step by step.

Choosing between the three

You needUseRound tripsCaching
Any write, a complete record, or anything the other two cannot giveRESTOne per record type. A properties selector can bring related records into the same response, which saves the follow-up requestsNot cached by the platform: every request runs
One screen's data from several record typesGraphQLOne for all of themNot cached by the platform; reads only
A shape no route gives, or logic and side effects that must run on the platformA synchronous jobOneNot cached; every call runs the whole job and is logged as a job run

None of the three is answered from the platform's page cache, which stores only the pages the platform renders itself. A headless front end caches API responses in its own layer and drops them when a job tells it that content changed (Knowing when content changed).

Lists include drafts by default

On a site with the default Show unpublished setting, a list read with a backend credential includes unpublished records, and a record read by its id is returned whether it is published or not. Send retrieveUnpublished=0 on every read that feeds a public page, as the examples above do: Drafts, preview and knowing when content changed.

Go deeper

Was this page helpful?

Your answer goes to the docs team.