Headless API

Contents

Now that we’ve created some content, let’s see how we can use it in a headless fashion. This chapter introduces Guillotine, the library that’s responsible for automatically generating GraphQL APIs for your content.

This chapter is optional. If you’re not interested in working headlessly, feel free to skip this chapter. You can always come back later ☺️

For the rest of this guide, whenever we’re demonstrating headless functionality, we’ll be using concepts introduced in this chapter. When we’re talking headless functionality specifically, it’ll be under a header that specifies it. If you’re not interested in working headlessly, feel free to skip these sections.

The tasks and examples in this chapter are based on work done in previous chapters (starting with the sandboxes chapter). If you want to follow along with the examples, make sure you’re all caught up. In this chapter, you won’t be building anything new, but we use the data set that we’ve created thus far.

The API explorer

The headless starter uses an XP library called Guillotine to dynamically expose all our editorial content. We’ll learn more about how this works later, but for now (with the default configuration) we can find the API at http://localhost:8080/site/default/draft/my-first-site/api.

If you have used a different name than "My First Site" for your site, you’ll have to modify the URL a little: Switch my-first-site for the path of your site. See the Guillotine reference docs for an explanation of how the URL is generated.

After navigating to the API, you’ll find an interactive API explorer for GraphQL APIs waiting for you. You enter your query in the text area to your left and when you execute it, you’ll get the results of the query on the other side of the screen.

There are tabs marked "schema" and "docs" on the right-hand side of the window. You can use these to look at the structure of the data that you can query for.
An empty GraphQL playground
Figure 1. The API explorer

GraphQL is a graph "query language". It allows you to create highly specific queries for exactly the content that you need (see the GraphQL docs to learn more).

Guillotine queries

The guillotine field allows you to query the API for content. We’ll see more advanced examples of this later in this chapter, but for now we’ll focus on the top level queries.

The API explorer offers completion candidates for fields. You can use this to interactively explore the API. You can also open the schema and documentation from the right-hand side menu tabs.

The guillotine field has sub-properties that you can query for. Each instance of the API runs on a site, so we can ask Guillotine about its site and about the children of the site. Copy the below query into your explorer and run it by clicking the play button.

You can also run queries by using the keyboard shortcut ctrl+enter/+enter .
A GraphQL query getting data about the site and its children
{
  guillotine {
    getSite {
      displayName
    }
    getChildren {
      displayName
    }
  }
}

The response to the query is returned as JSON. In this case, it would contain the name of the site (My First Site), as well as the immediate children of the site: the "artist" folder we created and the "Templates" that was already there.

The site info response
{
  "data": {
    "guillotine": {
      "getSite": {
        "displayName": "My First Site"
      },
      "getChildren": [
        {
          "displayName": "artists"
        },
        {
          "displayName": "Templates"
        }
      ]
    }
  }
}

Artist queries

Let’s look at writing slightly more complex queries that get us information about the artists we’ve created. To start with, let’s see if we can just fetch all the artists that we created.

A GraphQL query fetching all content below the "My First Site" site
{
  guillotine {
    getChildren(key: "${site}/artists")
    {
      displayName
    }
  }
}

The result of this query should list all the artists you created previously:

The result of the above query; all content nodes below "artists"
{
  "data": {
    "guillotine": {
      "getChildren": [
        {
          "displayName": "P!nk"
        },
        {
          "displayName": "Missy Elliott"
        },
        {
          "displayName": "Cardi B"
        }
      ]
    }
  }
}

Neat! Next, let’s try a slightly more complicated query. In this one, we’ll also get the data inside the content nodes and extract their names and alias fields. Because all content shares the displayName property, you can query for it without specifying what type it belongs to. However, to get fields that exist on the Artist type, you need to specifically tell GraphQL what type you’re looking for via inline fragments. Inline fragments start with …​ on and then specify the content type you want.

Learn more about inline fragments at the GraphQL documentation’s Inline Fragments section
A GraphQL query that fetches all artists and their data using inline fragments
{
  guillotine {
    getChildren(key: "${site}/artists/")
    {
      displayName
      ... on com_example_myproject_Artist { (1)
        data {
          name
          about
        }
      }
    }
  }
}
1 This inline fragment lets us extract extra data from content of the Artist type.

Execute this and your response should look a little something like this:

Artists and their aliases as returned by GraphQL
{
  "data": {
    "guillotine": {
      "getChildren": [
        {
          "displayName": "Missy Elliott",
          "data": {
            "name": "Melissa Arnette Elliott",
            "about": "Melissa Arnette Elliott (born July 1, 1971) is an American rapper, singer, songwriter, and record producer."
          }
        },
        {
          "displayName": "P!nk",
          "data": {
            "name": "Alecia Beth Moore",
            "about": "Alecia Beth Moore (born September 8, 1979), known professionally as Pink (stylized as P!nk), is an American singer and songwriter."
          }
        },
        {
          "displayName": "Cardi B",
          "data": {
            "name": "Belcalis Marlenis Almánzar",
            "about": "Belcalis Marlenis Almánzar (born October 11, 1992), known professionally as Cardi B, is an American rapper and songwriter."
          }
        }
      ]
    }
  }
}

Advanced querying

When making a Guillotine query, you can use the query field underneath guillotine to specify what you want. This field accepts a number of optional, named arguments to help you hone in on exactly what you’re looking for. These arguments are:

contentTypes

The content types we’re looking for. Can either be a lone string if you’re looking for a single content type, or a list if you want to search among multiple types.

filters

A list of filters to apply to the results after the query has been executed. We won’t get into them here, so if you want to learn more about them, head to the filters documentation.

first

Specify how many nodes you want at most in your result. If your query yields a list of results, you can specify that you only want the first n results, where n is a positive integer. Works well in concert with offset.

offset

Specify how many results you want to cut from the beginning of the list of results. Most commonly used for pagination.

query

A NoQL query string.

sort

A NoQL sort string.

For instance, using the data set we’ve created previously in this guide, we could run a query like this:

A Guillotine query using some of the arguments mentioned.
{
  guillotine {
    query(
      first: 2,
      offset: 0,
      contentTypes:
      [ "com.example.myproject:artist", (1)
	"com.example.myproject:animal" ],
      sort: "displayName desc") {
      displayName
      contentType {
        displayName
      }
    }
  }
}
1 To query for multiple types, enclose them in square brackets and separate them with commas.
If a content type doesn’t exist, such as in the above query, GraphQL will process the query as normal: it just won’t find any results of that type.

This query would produce a result like the following:

Guillotine query result
{
  "data": {
    "guillotine": {
      "query": [
        {
          "displayName": "P!nk",
          "contentType": {
            "displayName": "Artist"
          }
        },
        {
          "displayName": "Missy Elliott",
          "contentType": {
            "displayName": "Artist"
          }
        }
      ]
    }
  }
}

To perform a more advanced query, you can use the query parameter. The following query uses the n-gram query functionality to find all the artists that mention rap somewhere in their data.

A Guillotine query using nGram search functionality
{
  guillotine {
    query(
      contentTypes: "com.example.myproject:artist"
      query: "ngram('_allText', 'rap')"
      sort: "displayName asc") {
      displayName
    }
  }
}

The result would look something like this:

Rap results
{
  "data": {
    "guillotine": {
      "query": [
        {
          "displayName": "Cardi B"
        },
        {
          "displayName": "Missy Elliott"
        }
      ]
    }
  }
}

Naturally, you can also use the standard boolean operators to combine or negate clauses. If you wanted to find all artists that mention both rapping and singing, you could try a query like this:

Querying for artists who both rap and sing
{
  guillotine {
    query(
      contentTypes: "com.example.myproject:artist"
      query: "ngram('_allText', 'rap') AND ngram('_allText', 'sing')"
      sort: "displayName asc") {
      displayName
    }
  }
}

And find out that with our data set, there’s only one:

Rappers and singers
{
  "data": {
    "guillotine": {
      "query": [
        {
          "displayName": "Missy Elliott"
        }
      ]
    }
  }
}

The Guillotine library

If you’ve been wondering how the GraphQL API we’ve been querying up until now was created: the Guillotine library is the answer. The Guillotine library analyzes all the content in your applications and generates an API from that. It' s an optional library that comes preconfigured with the headless starter that we used back when we first created our app in a previous chapter. Guillotine provides direct, typed, and documented access to content within your site.

If you’re familiar with GraphQL, you might have noticed that we have not performed any mutations yet. The reason for that is simple: Guillotine only exposes the read-only part of the Enonic Content API, so mutations are not available.

Integrating Guillotine

When we created an app in a previous chapter, we used the headless starter, which comes with Guillotine integrated. As such, we haven’t had to worry ourselves with how to integrate it with XP. However, to better understand how it works, let’s have a little peek under the hood.

To embed Guillotine in your app, add a dependency on the library in the build.gradle at the root of your project. The build.gradle file that comes with the headless starter will look something like this:

A build.gradle file as included with the headless starter
plugins {
    id 'com.enonic.xp.app' version '3.0.0'
}

apply plugin: "com.enonic.xp.app"

app {
    name = "${appName}"
    displayName = "${appDisplayName}"
    vendorName = "${vendorName}"
    vendorUrl = "${vendorUrl}"
    systemVersion = "${xpVersion}"
}

dependencies {
    compile "com.enonic.xp:portal-api:${xpVersion}"
    include "com.enonic.xp:lib-websocket:${xpVersion}"
    include "com.enonic.lib:lib-guillotine:5.1.0" (1)
    include "com.enonic.lib:lib-graphql-playground:0.0.1"
}

repositories {
    mavenLocal()
    mavenCentral()
    xp.enonicRepo( 'dev' )
}
1 This line includes Guillotine version 5.1.0

This tells XP that we’re going to be using the library. However, it doesn’t do anything just yet. To make it work, we’re going to need to create a mapping and a controller. Both of these concepts will be explained in more detail in a later chapter, but the short form is that a mapping routes HTTP requests to a path to a controller. The controller is then responsible for processing and responding to the HTTP request.

The headless starter takes care of this for us by mapping requests to an /api path to a controller named graphql.js using the mappings element:

A site.xml file with mappings
<?xml version="1.0" encoding="UTF-8"?>
<site>
  <form />
  <mappings>
    <mapping controller="/controllers/graphql.js" order="50"> (1)
      <pattern>/api</pattern> (2)
    </mapping>
  </mappings>
</site>
1 This tells XP that requests that match this mapping’s pattern should be routed to the /controllers/graphql.js controller
2 This pattern tells XP that what requests to send to this mapping’s controller. In this case, it’s any requests to <site-url>/api, where <site-url> is the URL for your site.

The controller takes care of generating the schema and providing the API endpoints and logic. The headless starter comes with a default controller that provides the schema and the GraphQL playground that we’ve been using thus for. It’s too long to include in this guide, but if you want to have a look at it, the headless starter GraphQL controller is available on GitHub.

To learn more about the Guillotine library, how it works, and how you deploy it, check out the Guillotine reference docs.

Learn more

For a detailed overview of the node query language and an extensive example selection, the NoQL reference is the place to go. For more query examples and information about how to get up and running with Guillotine, you can check out the headless CMS intro guide.

If you want to learn more about GraphQL, the Introduction to GraphQL documentation is a good place to start.

Contents