Resource Entities
Table of contents
Entities overview
With Native external references, we introduce the following new entity types that allow us to model data from third-party systems, such as MockShop, in Contentful:
Resource Provider
- A third-party system that provides resources. Each provider can have multiple resource types. In our tutorial example, this is a MockShop provider.Resource Type
- A specific type of resource (not the resource itself) provided by a resource provider. In our tutorial example, this is Product.
Below is a representation of how a Resource Provider is structured, using the MockShop app as an illustrative example.
{
"sys": { "id": "MockShop" },
"type": "function",
"function": {
"sys": {
"type": "Link",
"linkType": "Function",
"id": "externalResources"
}
}
}
We are representing Resource Types in a similar structure:
{
"sys": { "id": "MockShop:Product" },
"name": "Product",
"defaultFieldMapping": {
"title": "{ /title }",
"subtitle": "Product ID: { /id }",
"image": {
"url": "{ /featuredImage/url }",
"altText": "{ /featuredImage/altText }"
}
}
}
Code walkthrough
The example app is using Functions to provide a connection between Contentful and the MockShop API. In the functions/mockShop.ts
file we are defining events that are necessary for Native external references to function properly:
search
- retrieval of specific content based on search querieslookup
- retrieval of specific content based on URNs (IDs)graphql.resourcetype.mapping
- retrieval of resource type mappings, which determines what fields in Contentful map to an external type in Graph API.graphql.query
- handles GraphQL queries for the external third-party API.
Search request
Search handler expects the following shape for outgoing requests:
type ResourcesSearchRequest = {
type: 'resources.search';
resourceType: string;
query?: string;
locale?: string;
limit: number;
pages?: {
nextCursor: string;
};
};
Property | Type | Description |
---|---|---|
type |
resources.search (required) |
Identifier for the type of the event. |
resourceType |
string (required) |
String consisting of the name of the provider and the resource within the provider, in a format {Provider}:{Type} , eg. MockShop:Product . |
query |
string (optional) |
Search query to be passed to Contentful Function, which in turn passes it down to the 3rd party system's search API. |
locale |
string (optional) |
The locale code that is relevant to the context of where the results will be shown in the web app. For example, the locale of the field where the + Add content button was pressed. |
limit |
number (required) |
Number defining the maximum items that should be returned. |
pages |
object (optional) |
|
pages.nextCursor |
string (required) |
Cursor string pointing to the specific page of results to be used as a starting point for the request. |
Lookup request
Lookup handler expects the following shape for outgoing requests:
type Scalar = string | number | boolean;
type ResourcesLookupRequest<
L extends Record<string, Scalar[]> = Record<string, Scalar[]>
> = {
type: 'resources.lookup';
resourceType: string;
lookupBy: L;
locale?: string;
limit?: number;
pages?: {
nextCursor: string;
};
};
Property | Type | Description |
---|---|---|
type |
resources.lookup (required) |
Identifier for the type of the event. |
resourceType |
string (required) |
String consisting of the name of the provider and the resource within the provider, in a format {Provider}:{Type} , eg. MockShop:Product . |
| lookupBy
| object
(optional) | |
| lookupBy.urns[]
| string[]
(required) | List of IDs of entities to be fetched. |
| locale
| string
(optional) | The locale code that is relevant to the context of where the results will be shown in the web app. For example, the locale of the field where the resource linked. |
| limit
| number
(required) | Number defining the maximum items that should be returned. |
| pages
| object
(optional) | |
| pages.nextCursor
| string
(required) | Cursor string pointing to the specific page of results to be used as a starting point for the request. |
Lookup and Search response
Both events return the same shape of the response:
type Product = {
urn: string;
title: string;
featuredImage?: {
url: string;
altText?: string;
};
badge: {
variant: string,
label: string
};
};
type ResourcesSearchResponse = {
items: Product[];
pages: {
nextCursor?: string;
};
};
type ResourcesLookupResponse = {
items: Product[];
pages: {
nextCursor?: string;
};
};
Property | Type | Description |
---|---|---|
items |
Product[] (required) |
List of returned products. |
pages |
object (required) |
|
pages.nextCursor |
string (optional) |
Cursor string to be used to request next page of results. |
Functions are designed without prior knowledge of the response data structure from third-party systems. Therefore, an additional transformation is required to ensure that all Function events return responses with a consistent shape of Product
s. This transformation is carried out by the withBadge
and withUrn
functions located in the function/utils.ts
file. In our example, we are expecting Resource to conform to a particular shape:
type Product = {
urn: string;
title: string;
featuredImage?: {
url: string;
altText?: string;
};
badge: {
variant: string,
label: string
};
};
GraphQL query request
GraphQL Query handler expects the following shape for outgoing requests:
type GraphQLQueryRequest = {
type: 'graphql.query';
query: string;
isIntrospectionQuery: boolean;
variables: Record<string, unknown>;
operationName?: string;
};
Property | Type | Description |
---|---|---|
type |
graphql.query (required) |
Identifier for the type of the event. |
query |
string (required) |
String consisting the graphql query to be passed to Contentful Function, which it turn passes it down to third party system. |
isIntrospectionQuery |
boolean (required) |
boolean indicating whether the query is an introspection request (to retrieve the schema) or a normal GraphQL query (to fetch data). |
variables |
Object (required) |
Variables required to run the graphql query to fetch the data from third party system. |
operationName |
string (optional) |
Operation name is an optional, descriptive identifier for graphql query. |
GraphQL query response
The GraphQL query response adheres to the standard GraphQL specification.
/**
* @see https://spec.graphql.org/October2021/#sec-Response
*/
type GraphQLQueryResponse = {
data?: Record<string, any> | null;
errors?: readonly Record<string, any>[];
extensions?: Record<string, unknown>;
};
Value | Description |
---|---|
data |
The data entry in the response will be the result of the execution of the requested operation. |
errors |
The errors entry in the response is a non-empty list of errors, where each error is a map. |
extensions |
GraphQL services may provide an additional entry to errors with key extensions. |
GraphQL resourcetype mapping request
GraphQL resourcetype mapping handler expects the following shape for outgoing requests:
type GraphQLResourceTypeMappingRequest = {
type: 'graphql.resourcetype.mapping'
resourceTypes: {
resourceTypeId: string;
}[];
};
Property | Type | Description |
---|---|---|
type |
graphql.resourcetype.mapping (required) |
Identifier for the type of the event. |
resourceTypes |
Array<{ resourceTypeId: string }> (required) |
Array of combination of external resource provider and resource types, for example MockShop:Product . |
GraphQL resourcetype mapping response
Here is the typing for GraphQLResourceTypeMapping
:
type GraphQLResourceTypeMapping = {
resourceTypeId: string
graphQLOutputType: string
graphQLQueryField: string
graphQLQueryArguments: Record<string, string>
}
GraphQLResourceTypeMapping
would have the following values for the product
field:
Value | Description |
---|---|
resourceTypeId: 'MockShop:Product' |
Resource Provider (a third-party system) combined with Resource Type (a specific type from the third-party system). |
graphQLQueryField: 'product' |
Field name in the query object of the given GraphQL.schema. |
graphQLOutputType: 'Product' |
The external GraphQL type from the third-party API. |
graphQLQueryArguments: { id: '/urn' } |
Arguments that match for the field. id matches the argument for the product field. If left empty, an error for invalid arguments is returned. |
Property mapping for rendering in the Web app
The Contentful web app displays entries using components that require specific data structures to fill their UI elements.
The mapping between these components and external system data is established using JSON pointers. This mapping is defined in the defaultFieldMapping
property of each Resource Type and must adhere to the structure used for mapping the values shown in the entry component:
Property | Type |
---|---|
title |
string (required) |
subtitle |
string (optional) |
image |
object (optional) |
image.url |
string (required) |
The definitions of Product
Resource Type representations can be found in the src/tools/entities/product.json
file, respectively.
Create additional resource type
In our example, we focus on a single Resource Type: Product
, because the MockShop API provides only one type: product
. If you want to include additional resource types, consider the following:
- Discover more entities available in the third party system.
- Create a new entity file in
src/tools/entities/
, similar tosrc/entities/product.json
with a Resource Type definition. - Update the
src/tools/import.ts
file to import the newly created JSON file. - Update the resource entities creations script in
src/tools/create-resource-entities.ts
to include the new type. - Update the resources by running the creation script:
npm run create-resource-entities
. - Build the function and update the bundled version stored in Contentful:
npm run build
npm run upload