Querying with GraphQL

Learn how to query Umbraco Heartcore with GraphQL.

In this tutorial we will be looking at how we can fetch data from Umbraco Heartcore using GraphQL.

We will be using https://demo.heartcore.dev/ as a reference.

At the end of the tutorial we should be able to use the Umbraco Heartcore GraphQL API and be able to fetch all the content needed to render the page in with a single query.

Creating the Document Types

First, we will need to create some Document Types, and as a start, we will create some element types.

Start by creating a folder named Elements under the Document Types folder in the Settings section of the Backoffice.

In that folder create the following Document Types:

Text and Image

Alias: textAndImage

Add a new group called Content with the following properties:

NameAliasProperty EditorProperty Editor Configuration

Title

title

Textstring

Use defaults

Text

text

Rich Text Editor

Use defaults

Image

image

Media Picker

Pick multiple items: not checked Pick only images: checked Disable folder select: checked

Show large image

showLargeImage

Checkbox

Use defaults

Then under permissions check Element Type

Unique Selling Point

Alias: uniqueSellingPoint

Add a new group called Content with the following properties:

NameAliasProperty EditorProperty Editor Configuration

Image

image

Media Picker

Pick multiple items: not checked Pick only images: checked Disable folder select: checked

Title

title

Textstring

Use defaults

Text

text

Textarea

Use defaults

Link

link

Multi Url Picker

Max number of items: 1

Then under permissions check Element Type


Then create another folder called Compositions and create the following Document Types in that folder:

Elements Composition

Alias: elementsComposition

Add a new group called Elements with the following properties:

NameAliasProperty EditorProperty Editor Configuration

Elements

elements

Nested Content

Document Types: Element Type: Text and image, Group: Content, Template: {{title}} Confirm Deletes: checked Show icons: checked Hide label: checked

Then click the Reorder button and change the value for Elements from 0 to 15

Hero Composition

Alias: heroComposition

Add a new group called Hero with the following properties:

NameAliasProperty EditorProperty Editor Configuration

Image

heroImage

Media Picker

Pick multiple items: not checked Pick only images: checked Disable folder select: checked

Title

heroTitle

Textstring

Use defaults

Subtitle

heroSubtitle

Textstring

Use defaults

Unique Selling Points Composition

Alias: uniqueSellingPointsComposition

Add a new group called Unique Selling Points with the following properties:

NameAliasProperty EditorProperty Editor Configuration

Title

uniqueSellingPointsTitle

Textstring

Document Types: Use defaults

Unique Selling Points

uniqueSellingPoints

Nested Content

Document Types: Element Type: Unique Selling Point, Group: Content, Template: {{title}} Confirm Deletes: checked Show icons: not checked Hide label: checked

Then click the Reorder button and change the value for Elements from 0 to 20


At the root create the following Document Types:

Textpage

Alias: textpage

Click on Compositions and select Elements Composition and Hero Composition

Frontpage

Alias: frontpage

Add a new group called Footer with the following properties:

NameAliasProperty EditorProperty Editor Configuration

Title

footerTitle

Textstring

Use defaults

Links

footerLinks

Multi Url Picker

Use defaults

Then click on Compositions and select Elements Composition, Hero Composition and Unique Selling Points Composition, click on Submit.

On the permissions tab check Allow at root and add Textpage to the Allowed child node types property.

Then go to the Content section and create a new Frontpage with the name Home and create some subpages.

Querying the GraphQL API

The GraphQL endpoint accepts POST requests with the content type application/json, the body should be an object containing as a minimum a query property, for example

{
  "query": "..."
}

For the rest of this tutorial the GraphQL queries are written in plain text that can be executed with the GraphQL Playground.

Lets start with a basic query that fetches the name, url, and heroTitle properties from the Frontpage.

{
  frontpage(url: "/home/") {
    name
    url
    heroTitle
  }
}

Result:

{
  "data": {
    "frontpage": {
      "name": "Home",
      "url": "/home/",
      "heroTitle": "Umbraco Heartcore"
    }
  }
}

Making the Query Generic

Up until now, we have been working on a single Document Type. In most cases this is fine, but since we want to dynamically fetch content based on the URL we can use the content field.

The content field returns the Content interface type that contains the default fields. To allow us to query the fields on the derived types we can use fragments.

A fragment allows us to query data on the underlying concrete type.

{
  content(url: "/home/") {
    name
    ... on Frontpage {
      heroTitle
      heroSubtitle
      heroImage {
        url(width: 1980, height: 430, cropMode: CROP)
      }
    }
  }
}

This returns the following JSON:

{
  "data": {
    "content": {
      "name": "Home",
      "heroTitle": "Umbraco Heartcore",
      "heroSubtitle": "Umbraco Heartcore is a headless CMS by Umbraco. With Heartcore, you can use the Umbraco backoffice to manage content and media that's ready to be displayed on any device.",
      "heroImage": {
        "url": "https://media.umbraco.io/demo-headless/8d832c5cee78cf8/umbraco-heartcore-preview.png?mode=crop&width=1980&height=430&upscale=false"
      }
    }
  }
}

The query is fetching the name field which exists on the Content interface, it also fetches the heroTitle, heroSubtitle and heroImage on the Frontpage type.

Since the heroImage is a Media picker we can pass arguments to the url field telling the server to generate an url with the Image Cropper query string parameters.

Querying Composition Fields

There is one problem with our query though. Try changing the url argument to a subpage and see whats happening.

As you can see we don't get any data back for the hero fields, even though our Document Type has those fields. This is because we are telling GraphQL that we only want them on the Frontpage type. You might be tempted to fix this by adding another fragment on the Textpage type which would work, but remember we created a Document Type that only contains hero fields which we have added as a Composition to both the Frontpage and Textpage Document Types. We can change the ... on Frontpage fragment to ... on HeroComposition and we will now get the expected data back.

Now our query looks like this:

{
  content(url: "/home/") {
    name
    ... on HeroComposition {
      heroTitle
      heroSubtitle
      heroImage {
        url(width: 1980, height: 430, cropMode: CROP)
      }
    }
  }
}

Querying Nested Content

Since Nested Content is an interface named Element we can query the fields by the specific type using fragments.

{
  frontpage(url: "/home/") {
    elements {
      ... on TextAndImage {
        title
        text
        showLargeImage
        image {
          small: url(width: 320, height: 240, cropMode: CROP)
          medium: url(width: 480, height: 360, cropMode: CROP)
          large: url(width: 1024, height: 768, cropMode: CROP)
        }
      }
    }
  }
}

Now try to write a query that fetches the title, text, link and image fields for the Unique Selling Points property.

The only thing we are missing now to be able to render the complete page is the main navigation and the footer.

The main navigation is showing the children of the frontpage, we can fetch them with the following query:

{
  frontpage(url: "/home/") {
    children {
      edges {
        node {
          name
          url
        }
      }
    }
  }
}

The footer can be fetched using the following query:

{
  frontpage(url: "/home/") {
    footerTitle
    footerLinks {
      name
      target
      type
      url
    }
  }
}

We can even combine the two queries into a single query:

{
  global: frontpage(url: "/home/") {
    mainNavigation: children {
      edges {
        node {
          name
          url
        }
      }
    }
    footerTitle
    footerLinks {
      name
      target
      type
      url
    }
  }
}

In the query above we are also using field aliases, this means that in the response data frontpage will be named global and children will be named mainNavigation.

{
  "data": {
    "global": {
      "mainNavigation": {
        "edges": [
          {
            "node": {
              "name": "What is a Headless CMS?",
              "url": "/home/what-is-a-headless-cms/"
            }
          },
          {
            "node": {
              "name": "What you get with Umbraco Heartcore",
              "url": "/home/what-you-get-with-umbraco-heartcore/"
            }
          }
        ]
      },
      "footerTitle": "About Umbraco",
      "footerLinks": [
        {
          "name": "Umbraco Website",
          "target": "_blank",
          "type": "EXTERNAL",
          "url": "https://umbraco.com/products/umbraco-heartcore"
        },
        {
          "name": "Umbraco Heartcore Documentation",
          "target": "_blank",
          "type": "EXTERNAL",
          "url": "https://docs.umbraco.com/umbraco-heartcore/"
        }
      ]
    }
  }
}

Using Variables

You might have noticed that the url argument is hardcoded in the query. This means that we will always get back the Content for that url. What we want is to pass the argument to the query dynamically.

We can do this by altering the query to include a variable instead of the hardcoded value, this can be done by replacing the argument with $url and wrapping the query with query($url: String).

query ($url: String!){
  content(url: $url) {
    name
    ... on HeroComposition {
      heroTitle
      heroSubtitle
      heroImage {
        url(width: 1980, height: 430, cropMode: CROP)
      }
    }
  }
}

And then we pass the variable in a separate JSON property called variables

{
  "query": "...",
  "variables": {
    "url": "/home/"
  }
}

Putting it all together

Now that we have all the individual parts we can combine it all into a single query that fetches all the data needed to display the pages on our site:

query($url: String!) {
  content(url: $url) {
    ...Hero
    ...UniqueSellingPoints
    ...Elements
  }
  global: frontpage(url: "/home/") {
    mainNavigation: children {
      edges {
        node {
          name
          url
        }
      }
    }
    footerTitle
    footerLinks {
      name
      target
      type
      url
    }
  }
}

fragment Hero on HeroComposition {
  heroTitle
  heroSubtitle
  heroImage {
    url(width: 1980, height: 430, cropMode: CROP)
  }
}

fragment Elements on ElementsComposition {
  elements {
    ... on TextAndImage {
      title
      text
      showLargeImage
      image {
        small: url(width: 320, height: 240, cropMode: CROP)
        medium: url(width: 480, height: 360, cropMode: CROP)
        large: url(width: 1024, height: 768, cropMode: CROP)
      }
    }
  }
}

fragment UniqueSellingPoints on UniqueSellingPointsComposition {
  uniqueSellingPointsTitle
  uniqueSellingPoints {
    ... on UniqueSellingPoint {
      title
      text
      link {
        name
        target
        type
        url
      }
      image {
        url
      }
    }
  }
}

Variables:

{
  "url":"/home/"
}

Last updated