DEV Community

loading...
Contentful

Exploring linked entries and assets in Contentful with JavaScript via REST and GraphQL

whitep4nth3r profile image Salma Alam-Naylor Originally published at contentful.com ・8 min read

The most frequently asked questions in DevRel when I started with Contentful were about how to display links or linked entries and assets inside the Contentful Rich Text field on the front end. It’s no secret that those of you who tuned in to my Twitch streams after I had started at Contentful saw me struggle with the concept of links as well! So, I set out to explore and investigate the inner workings of the Contentful REST API and GraphQL API in terms of linking assets and entries on a content type in order to understand how we can render links inside Contentful Rich Text fields.

What are links in Contentful?

If you’re looking for information on how to render linked assets and entries returned as part of the Contentful Rich Text field response using REST or GraphQL in JavaScript, check out this post.

Links are Contentful’s way of modelling relationships between content types and entries. Entries in Contentful can contain link fields that point to other assets or entries, and those entries can link to other assets or entries, and so on. For example:

  • A blog post can have an author
  • A team can have many authors
  • A company can have many teams

You can liken this to working with relational databases, where you would define one to one or one to many relationships within your data structures or models. For more information on the concept of links in Contentful, visit the documentation.

Here’s the content model that we’ll be working with in this article. The screenshot of the blog post content model below shows that the Author field is a Reference field type, which is a link.

Screenshot showing a blog post content model

TL;DR:

If you’re using the Content Delivery and Content Preview REST API, Contentful provides a number of SDKs (Software Development Kits) in the most popular programming languages. These will resolve your linked entries and assets for you. In this example, we’ll be taking a look at the JavaScript SDK.

If you’re using the GraphQL API, you control how your entries are resolved in the construction of your GraphQL query. And by understanding how the REST API works and how the SDKs resolve links, you’ll be all set.

Let’s take a look!

Requesting data from Contentful

The following examples focus on using the JavaScript ecosystem to query data from this example blog post. The example blog post is served on an application built with Next.js — but we won’t be going into Next.js in this post.

Using the REST API

Take this example request URL.

https://cdn.contentful.com/spaces/{{spaceId}}/environments/master/entries?access_token={{accessToken}}&content_type=blogPost&fields.slug=the-power-of-the-contentful-rich-text-field&include=10
Enter fullscreen mode Exit fullscreen mode

It is querying the Contentful Delivery API with the following parameters:

  • spaceId: Our space ID
  • accessToken: Our access token for the Content Delivery API
  • content_type: blogPost
  • fields.slug: the-power-of-the-contentful-rich-text-field (return the blogPost entry that has this slug)
  • include: 10 (return linked entries and assets up to 10 levels deep (this is the maximum include parameter value on the Content Delivery API) - we will unpack this later!)

The REST API response

The raw JSON response from the request above contains the following top level properties and nodes in a flat structure.

{
  "sys": {
    "type": "Array"
  },
  "total": 1,
  "skip": 0,
  "limit": 100,
  "items": [...],
  "includes: {...}
}
Enter fullscreen mode Exit fullscreen mode

The items array

items contains the requested entries (the entry with the matching slug in this case). Each entry contains a subset of the fields defined on the content type of this entry and some internal system information (sys). Notice how the linked author entry is missing the fields property. It only holds the sys information including the linkType and id.

"items": [
  {
    "sys": {...},
    "fields": {
      "title": "...",
      "slug": "the-power-of-the-contentful-rich-text-field",
      "author": {
        # This is a "Link"
        # and contains only a reference to the Author entry
        "sys": {
          "type": "Link",
          "linkType": "Entry",
          "id": "123456789"
        }
      },
    }
  }
]
Enter fullscreen mode Exit fullscreen mode

Where are the author fields? Let’s find out!

The includes object

The includes object contains two array nodes:

  1. "Entry" for all referenced entries in items (such as the the blog post author which we saw returned as a “type”: “Link” in the response above)
  2. "Asset" for all referenced assets in items (such as images, which might be a featured image on a blog post, for example)

In the case of the author, which is a linked entry on our blogPost, we see the full author object returned in includes.Entry[0] — including another link to an image asset.

"includes": {
 "Entry": [
  {
    "sys": {
      "space": {
        "sys": { //... }
      },
      "id": "123456789",
      "type": "Entry",
      "createdAt": "...",
      "updatedAt": "...",
      "environment": {
        "sys": { //... }
      },
      "revision": 1,
      "contentType": {
        "sys": {
          "type": "Link",
          "linkType": "ContentType",
          "id": "person"
        }
      },
      "locale": "en-US"
    },
    "fields": {
      "image": {
        "sys": {
          # Heres another link that we didnt find in the items array
          # due to it being nested deeper than 1 level in the object tree
          "type": "Link",
          "linkType": "Asset",
          "id": "555555555"
        }
      },
      "name": "Salma Alam-Naylor",
      "description": "This is the author description",
      "twitterUsername": "whitep4nth3r",
      "gitHubUsername": "whitep4nth3r",
      "twitchUsername": "whitep4nth3r",
      "websiteUrl": "https://whitep4nth3r.com"
    }
  },
 ]
}
Enter fullscreen mode Exit fullscreen mode

The response includes all the data that you need to render the blog post to the front end. However, the data is spread across items and includes, and you — as a developer — would expect all that data to be returned as one object, right? 🤯

For example, in React, you might want to do something like this to show the author’s name on the front end:

export default function BlogPost(props) {
  const { blogPost } = props;

  return (
    <div>
      <h1>{blogPost.fields.title}</h1>
      <h2>By {blogPost.fields.author.name}</h2>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

However, we need to do some more work before we can make this happen — we need to resolve the linked entries — and this is where we can use the Contentful JavaScript SDK.

Currently, the blogPost item references the author by sys.id:

"author": {
  "sys": {
    "type": "Link",
    "linkType": "Entry",
    "id": "123456789"
  }
}
Enter fullscreen mode Exit fullscreen mode

You could cross-reference the items[0].fields.author.sys.id with the includes.Entry array, find the item in the array that has the id that matches, and resolve the data from there. It sounds pretty straightforward in this example, but when your content model gets more complex with many entries linking to other entries, it could get unwieldy.

Let’s look at how the JavaScript SDK can help us out.

Under the hood, the JavaScript SDK uses the contentful-resolve-response package, which converts the raw nodes into a rich tree of data. The one limitation of the Contentful Delivery API to bear in mind is that it will only return linked entries up to a maximum of 10 levels deep that can be resolved.

Graph presenting how the rest API response feeds into the transformed content tree

Unpacking the include request parameter

Specify the depth of the resolved tree using the include parameter in the request to the API, either as a parameter on the GET request URL, like this:

https://cdn.contentful.com/spaces/{{spaceId}}/environments/master/entries?access_token={{accessToken}}&content_type=blogPost&fields.slug=the-power-of-the-contentful-rich-text-field&limit=1&include=10
Enter fullscreen mode Exit fullscreen mode

or via a call to the JavaScript SDK:

const post = await client
  .getEntries({
    content_type: "blogPost",
    limit: 1,
    include: 10,
    "fields.slug": "the-power-of-the-contentful-rich-text-field",
  })
  .then((entry) => entry)
  .catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Both examples above make the same request to the Contentful API — except the SDK example is resolving your linked entries as part of the process using contentful-resolve-response. Neat!

How the include parameter affects the length of the includes response

Say you have a blog post, which has a reference to an author, which has a reference to a team.

To visualise this in an object graph:

{
  "blogPost": {
    //...
    "fields": {
       "author": {
        //...
          "team": {
          //...
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

If you specify includes=1 in your request, your includes array on the response will contain one item in this example, the author object (1 level deep).

If you specify includes=2 in your request, your includes array on the response will contain two items, the author object and the team object. (2 levels deep).

If your blogPost had another top level reference, say a heroBanner, includes=1 would return both the author and heroBanner inside the includes array.

{
  "blogPost": {
    //...

    "fields": {
      //...

      "heroBanner": {
        //...
      },

      "author": {
        //...

        "team": {
          //...
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Regardless of the include depth you specify — the SDK — which uses the contentful-resolve-response package, will link all available and responded entries and assets that are returned in the includes response.

Read more about the include param on the Contentful docs.

Using the GraphQL API

The Contentful GraphQL API doesn’t require an SDK to handle linked entries — but understanding the concepts covered previously helps us out here.

The main differences between the REST API and GraphQL API

  • The response from the GraphQL API gives you a rich object graph as standard (so you won’t find includes in the response).
  • With GraphQL you specify the equivalent depth of the includes response through the construction of your query. The only limit here is the complexity of your GraphQL query. Technically, if you construct your query cleverly, you can reach data hundreds of levels deep! Read more about GraphQL complexity limits here.

Here’s the GraphQL query that we would use to fetch the same blog post data with the author name and image as referenced in the first example:

const query = `{
    blogPostCollection(limit: 1, where: {slug: "the-power-of-the-contentful-rich-text-field"}) {
      items {
        sys {
          id
        }
        title
        slug
        author {
          name
          # more author fields … 
          image {
            sys {
              id
            }
            url
            # more image fields ...
          }
        }
      }
    }
  }`;
Enter fullscreen mode Exit fullscreen mode

And here’s how we can query the Contentful GraphQL API using fetch:

const fetchOptions = {
  method: "POST",
  headers: {
    Authorization: `Bearer ${ACCESS_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ query }),
};

const response = await fetch(`https://graphql.contentful.com/content/v1/spaces/{SPACE_ID}`, fetchOptions).then((response) => response.json());
Enter fullscreen mode Exit fullscreen mode

To compare this query to the include levels in the REST API:

  • Level 1: blogPost
  • Level 2: blogPost.author
  • Level 3: blogPost.author.image

The GraphQL API response

Due to how we constructed our GraphQL query to fetch the linked entries and assets, the raw response from the GraphQL contains the data for the linked assets and entries in the nodes we would expect — at a content type level only.

Here’s the response for the above query from the GraphQL API:

{
  "data": {
    "blogPostCollection": {
      "items": [
        {
          "sys": {
            "id": "53PLFh5VLIotcvMqR6VsnO"
          },
          "title": "The power of the Contentful Rich Text field",
          "slug": "the-power-of-the-contentful-rich-text-field",
          "author": {
            "name": "Salma Alam-Naylor",
            "image": {
              "sys": {
                "id": "rImaN1nOhnl7aJ4OYwbOp"
              },
              "url": "https://images.ctfassets.net/.../image.png",
             }
          },
        }
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

In the response above, the data for author appeared in the node tree exactly where we expected it, and we can access the name on the front end — for example, via data.blogPostCollection.items[0].author.name — without having to use an SDK to resolve the entries.

The include depth is inferred by the construction of your GraphQL query

In comparison to the REST API, where you usually fetch the blog post data and link the entries after the fact, a GraphQL API query is entirely flexible to your needs. There’s always the caveat, however, that a complex GraphQL query with many nested link assets and entries might surpass the maximum complexity permitted on the GraphQL API. Read more about GraphQL complexity limits here.

In conclusion

Understanding the structure of the data responses from Contentful and how linked assets are returned and then resolved via the Contentful SDKs, empowers you to choose which APIs and methods are best suited to your applications. And, hey, if you want to resolve the linked assets and entries yourself, then you’re well equipped.

Check out some further reading on how you can resolve linked assets and entries from the Contentful Rich Text field response in both the REST API and GraphQL API.

And remember, build stuff, learn things and love what you do.

Discussion (0)

pic
Editor guide