Skip to main content

CDN

TakeShape offers a GraphQL CDN out-of-the-box and has several features to help you optimize your API by making queries cacheable at the CDN level. This improves the performance of your API and conserves your rate limit. This is most helpful for read-focused applications where the content is more stable. This guide will teach you how to take advantage of these features.

This builds on the powerful caching features TakeShape offers at the platform level. Learn more about those in our API Indexing Guide.

Using the TakeShape CDN

The GraphQL CDN is available on paid plans. The CDN URL can be found on the dashboard when you log in. It looks like this:

https://cdn.takeshape.io/project/YOUR_PROJECT_ID/production/graphql

The path part of this URL has the same structure as the origin API endpoint, including the branch name. The only difference is that the domain is cdn.takeshape.io instead of api.takeshape.io.

Your project's CDN endpoint can handle GraphQL queries using both GET and POST. As you will see below, you will want to use GET as often as possible for cacheability.

Make your queries more cacheable

Let's look at an example TakeShape project schema and walk through how to query it optimally and add caching hints to the schema.

Starting Point

Let's say you have a schema that is already set up to index assets from Contentful. You've even extended the Contentful_Asset shape to query Shopify product data based on an ID stored on the asset. Here is an example of how that could be accomplished:

View example schema
{
"defaultLocale": "en-us",
"locales": ["en-us"],
"queries": {
"getIndexedContentfulAssetList": {
"shape": "PaginatedList<Contentful_Asset>",
"resolver": {
"shapeName": "Contentful_Asset",
"name": "takeshape:queryApiIndex",
"service": "takeshape",
"options": { "indexedShape": "Contentful_Asset" }
},
"description": "Fetch Conftentful_Asset data from the API Index.",
"args": "TSListArgs<Contentful_Asset>"
},
"Contentful_assetCollection": {
"resolver": {
"name": "graphql:query",
"service": "contentful",
"fieldName": "assetCollection"
},
"args": {
"type": "object",
"properties": {
"skip": { "type": "integer", "default": 0 },
"limit": { "type": "integer", "default": 100 },
"preview": { "type": "boolean" },
"locale": { "type": "string" },
"where": { "@ref": "contentful:AssetFilter" },
"order": { "type": "array", "items": { "@ref": "contentful:AssetOrder" } }
}
},
"shape": "Contentful_AssetCollection"
},
"Shopify_product": {
"resolver": {
"name": "graphql:query",
"service": "shopify",
"fieldName": "product"
},
"args": {
"type": "object",
"properties": {
"id": {
"type": "string",
"@tag": "id",
"description": "The ID of the Product to return."
}
},
"required": ["id"]
},
"shape": "Shopify_Product"
}
},
"mutations": {},
"shapes": {
"Contentful_Asset": {
"id": "Contentful_Asset",
"name": "Contentful_Asset",
"title": "Contentful Asset",
"cache": {
"enabled": true,
"triggers": [{ "type": "schedule", "loader": "list", "interval": 1440 }],
"idField": "url"
},
"loaders": {
"list": {
"query": "contentful:Query.assetCollection",
"pagination": {
"type": "offset",
"offsetArg": "skip",
"pageSizeArg": "limit",
"itemsPath": "items",
"itemTotalPath": "total"
}
}
},
"schema": {
"extends": [
{ "@ref": "contentful:Asset" },
{
"type": "object",
"properties": {
"shopifyProduct": {
"@ref": "shopify:Product",
"@resolver": {
"name": "delegate",
"to": "shopify:Query.product",
"args": { "ops": [{ "path": "id", "mapping": "$source.title" }] }
},
"title": "Shopify Product"
}
}
}
]
}
},
"Shopify_Product": {
"id": "Shopify_Product",
"name": "Shopify_Product",
"title": "Shopify Product",
"schema": { "extends": [{ "@ref": "shopify:Product" }] },
"interfaces": [
"shopify:HasMetafieldDefinitions",
"shopify:HasMetafields",
"shopify:HasPublishedTranslations",
"shopify:LegacyInteroperability",
"shopify:Navigable",
"shopify:Node",
"shopify:OnlineStorePreviewable",
"shopify:Publishable"
],
"cache": { "enabled": true }
}
},
"workflows": {},
"forms": {},
"schemaVersion": "3.42.0",
"apiVersion": "2",
"services": {
"contentful": {
"id": "contentful",
"title": "Contentful",
"namespace": "Contentful",
"provider": "contentful",
"serviceType": "graphql",
"authenticationType": "bearer",
"options": {
"endpoint": "https://graphql.contentful.com/content/v1/spaces/84ssynxl6f3z",
"space": "84ssynxl6f3z",
"introspectedAt": "2024-06-06T19:50:17.873Z"
}
},
"shopify": {
"id": "shopify",
"title": "Shopify",
"namespace": "Shopify",
"provider": "shopify",
"serviceType": "graphql",
"authenticationType": "custom",
"options": {
"shop": "gregs-takeshape-store",
"endpoint": "https://gregs-takeshape-store.myshopify.com/admin/api/2023-04/graphql.json",
"introspectedAt": "2024-06-06T19:35:40.908Z"
}
}
}
}

Typically you might query your API using POST like this:

export GRAPHQL_API_KEY="xxxx"
export GRAPHQL_ENDPOINT="xxxx"
curl -g \
-X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $GRAPHQL_API_KEY" \
-d '{"query":"query {getIndexedContentfulAssetList { items { title url shopifyProduct { title createdAt } } total }}"}' \
$GRAPHQL_ENDPOINT

This will work, but the CDN will not cache POST requests.

Using GET

Here is an example GET request. Note that the GraphQL query is in the URL.

export GRAPHQL_API_KEY="xxxx"
export GRAPHQL_ENDPOINT="xxxx"
curl -G \
-H "Authorization: Bearer $GRAPHQL_API_KEY" \
--data-urlencode 'query={"query":"query {getIndexedContentfulAssetList { items { title url shopifyProduct { title createdAt } } total }}"}' \
$GRAPHQL_ENDPOINT

This example works for short queries, but one limitation of GET is the URL length. GraphQL queries get complicated and can easily bump your URL above 8,192 bytes, which is the URL length limit for the TakeShape GraphQL CDN (and is around the typical limit for other popular CDNs as well). You can surpass this limit using Automatic Persisted Queries.

Automatic Persisted Queries

TakeShape's Automatic Persisted Queries feature will persist your GraphQL query string so that you don't need to send it every time you want to execute that query. This is supported out of the box with popular clients such as Apollo and urql.

To implement it yourself, include a SHA256 hash of your query (along with any variables) instead of the query itself, like this:

export GRAPHQL_API_KEY="xxxx"
export GRAPHQL_ENDPOINT="xxxx"
export QUERY='{"query":"query {getIndexedContentfulAssetList { items { title url shopifyProduct { title createdAt } } total }}"}'
export QUERY_HASH=$(echo -n "$QUERY" | openssl sha256 | awk '{print $2}')
curl -G \
-H "Authorization: Bearer $GRAPHQL_API_KEY" \
--data-urlencode "extensions={\"persistedQuery\":{\"version\": 1, \"sha256Hash\": \"$QUERY_HASH\"}}" \
$GRAPHQL_ENDPOINT

If the query isn't currently persisted, the API responds with an error to let us know:

{ "errors": [{ "message": "Persisted Query Not Found", "extensions": { "code": "PERSISTED_QUERY_NOT_FOUND" } }] }

Now let's POST our query (to avoid the URL length limit) with the hash in the query parameters so that we get a response and also persist the query at the same time:

export GRAPHQL_API_KEY="xxxx"
export GRAPHQL_ENDPOINT="xxxx"
export QUERY='{"query":"query {getIndexedContentfulAssetList { items { title url shopifyProduct { title createdAt } } total }}"}'
export QUERY_HASH=$(echo -n "$QUERY" | openssl sha256 | awk '{print $2}')
curl -g \
-X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $GRAPHQL_API_KEY" \
-d "$QUERY" \
"$GRAPHQL_ENDPOINT?extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A%201%2C%20%22sha256Hash%22%3A%20%22$QUERY_HASH%22%7D%7D"

You'll get your query's response back as usual. Now if you execute the previous GET request again (with just the hash and the query string not specified) you'll get data back from that too because the query was persisted.

Add Cache Hints

Now that you are sending requests that your CDN can cache, you'll want to add cache hints to your schema. This will allow TakeShape to calculate Cache-Control headers that the CDN can use.

Looking at our example schema from before, there are 2 shapes that we will add cache hints to. When doing this, think about how long content for each shape should be cached. In this schema, the Contentful_Asset shape is indexed on a schedule with an interval of 1440 minutes (daily). But what if you know that your Contentful assets do not update very often and you're comfortable caching them for longer than that? Let's add a maxAge to the Contentful_Asset cache configuration with the larger value of 259200 seconds (3 days)

"Contentful_Asset": {
"id": "Contentful_Asset",
"name": "Contentful_Asset",
"title": "Contentful Asset",
"cache": {
"enabled": true,
"triggers": [{"type": "schedule", "loader": "list", "interval": 1440}],
"idField": "url",
"maxAge": 259200
},
...
}

Now let's assume that our Shopify products do update frequently, so we'll set the maxAge for that shape to 86400 seconds (1 day).

"Shopify_Product": {
"id": "Shopify_Product",
"name": "Shopify_Product",
"title": "Shopify Product",
"cache": {
"enabled": true,
"maxAge": 86400
},
...
}

Now that these cache hints have been added, you'll see that TakeShape sets the cache-control header based on which shapes are used in the query.

For example:

{
getIndexedContentfulAssetList {
items {
title
url
shopifyProduct {
title
createdAt
}
}
total
}
}

This query includes data from both Contentful_Asset and Shopify_Product. In this case, the lowest maxAge is used; that is, the maxAge of Shopify_Product. The cache-control header will have the value max-age=86400.

Another example:

{
getIndexedContentfulAssetList {
items {
title
url
}
total
}
}

This time we are not selecting any Shopify_Product data. In this case, the maxAge from Contentful_Asset will be used and the cache-control header will have the value max-age=259200.

Conclusion

This guide demonstrated the tools TakeShape offers to cache your GraphQL API at the CDN level. The particulars of how to use those tools will always be a little different for every application. If you have any questions about how to use these features in your project, feel free to get in touch using the live chat in the help menu.