Was this page helpful?

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 queries
  • lookup - 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 Products. 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 to src/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