Exploring linked entries and assets in Contentful with JavaScript via REST and GraphQL
Tags: webdev, javascript, graphql, react
Published: 2021-04-22 11:01:36

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](https://www.contentful.com/developers/docs/concepts/rich-text/) field on the front end. It’s no secret that those of you who tuned in to my [Twitch streams](https://www.twitch.tv/whitep4nth3r) 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](https://www.contentful.com/blog/2021/04/14/rendering-linked-assets-entries-in-contentful/).

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](https://www.contentful.com/developers/docs/concepts/links/).

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o8mq57og3yvoy835h8vf.png)

## TL;DR:

If you’re using the [Content Delivery](https://www.contentful.com/developers/docs/references/content-delivery-api/) and [Content Preview](https://www.contentful.com/developers/docs/references/content-preview-api/) 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](https://nextjs-contentful-blog-starter.vercel.app/blog/the-power-of-the-contentful-rich-text-field). 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.

```bash
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
```

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.

```javascript
{
  "sys": {
    "type": "Array"
  },
  "total": 1,
  "skip": 0,
  "limit": 100,
  "items": [...],
  "includes: {...}
}
```

### 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`.

```javascript
"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"
        }
      },
    }
  }
]
```

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.

```javascript
"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": {
          # Here’s another link that we didn’t 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"
    }
  },
 ]
}
```

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:

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

  return (
    <div>
      <h1>{blogPost.fields.title}</h1>
      <h2>By {blogPost.fields.author.name}</h2>
    </div>
  );
}
```

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`:

```javascript
"author": {
  "sys": {
    "type": "Link",
    "linkType": "Entry",
    "id": "123456789"
  }
}
```

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](https://www.contentful.com/developers/docs/javascript/sdks/) can help us out.

Under the hood, the JavaScript SDK uses the [contentful-resolve-response](https://github.com/contentful/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](https://images.ctfassets.net/fo9twyrwpveg/1uBXFD4SNo7BWxM0XtEErB/bbb04c441d7385b5b63f67534e743b0a/TransformedContentTree.svg)

### 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:


```bash
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
```

or via a call to the JavaScript SDK:

```javascript
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);
```

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](https://github.com/contentful/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:

```javascript
{
  "blogPost": {
    //...
    "fields": {
       "author": {
        //...
          "team": {
          //...
        }
      }
    }
  }
}
```

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.

```javascript
{
  "blogPost": {
    //...

    "fields": {
      //...

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

      "author": {
        //...

        "team": {
          //...
        }
      }
    }
  }
}
```

Regardless of the `include` depth you specify — the SDK — which uses the [contentful-resolve-response](https://github.com/contentful/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.](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/links)

## Using the GraphQL API

[The Contentful GraphQL API](https://www.contentful.com/developers/docs/references/graphql/) 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](https://www.contentful.com/developers/docs/references/graphql/#/introduction/query-complexity-limits).

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:

```javascript
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 ...
          }
        }
      }
    }
  }`;
```

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

```javascript
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());
```

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:

```javascript
{
  "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",
             }
          },
        }
      ]
    }
  }
}
```

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](https://www.contentful.com/developers/docs/references/graphql/#/introduction/query-complexity-limits).

## 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](https://www.contentful.com/blog/2021/04/14/rendering-linked-assets-entries-in-contentful/).

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