1. Introduction
The CaaS platform enables companies to flexibly provide and use content across various digital channels.
It consists of the REST Interface (caas-rest-api) for managing and querying content via HTTP, and the CaaS repository (caas-mongo), the internal database for storing content.
This document is intended for users of the REST Interface of the CaaS platform and describes its functions and usage options. Instructions and guidance for operating and technically administering the platform can be found in the separate Operations Guide.
2. Basics of the REST Interface
The REST Interface allows management and querying of content and media as JSON documents via HTTP. It supports authentication and authorization via API Key or security token and returns results in JSON format. In addition to CRUD operations on documents, databases, collections, API Keys, and indexes can also be managed. The interface also offers features such as pagination, filtering, and reference resolution. Additionally, data can be flexibly queried and managed via custom aggregations and GraphQL endpoints.
2.1. Authentication
Every request to the REST Interface must be authenticated, otherwise it will be rejected. The different authentication methods are explained below.
2.1.1. API Key method
Requests with API Key must include an HTTP header with a bearer token: Authorization: Bearer <key>.
The value of key should be the value of the key attribute of the API Key used.
For more information, see Validation of API Keys.
2.1.2. Security token method
It is possible to generate a short-lived (up to 24 hours) security token for an API Key. The token includes the same permissions as the API Key it was generated for.
There are two ways to generate and use these tokens:
-
Query parameter
A GET request authenticated with an API Key to the endpoint/_logic/securetoken?tenant=<database>generates a security token. Such a token can only be issued for a specific database, regardless of whether the API Key has permissions for multiple databases. A parameter&ttl=<lifetime in seconds>is supported and optional. The security token is included in the JSON response.Any request to the REST Interface can optionally be authenticated via a query parameter
?securetoken=<token>. -
Cookie
A GET request authenticated with an API Key to the endpoint/_logic/securetokencookie?tenant=<database>generates a security token cookie. Such a cookie can only be issued for a specific database, regardless of whether the API Key has permissions for multiple databases. A parameter&ttl=<lifetime in seconds>is supported and optional. The response includes aSet-Cookieheader with the security token.All requests with this cookie are automatically authenticated.
2.2. API Keys
API Keys enable authentication and authorization of requests to the REST Interface. They contain a list of permissions that define which actions are allowed on which resources.
API Keys can be managed at two levels: globally or locally per database. Accordingly, there are two types of API Keys, which differ in their scope:
-
Global API Keys
Global API Keys are cross-database and are managed in theapikeyscollection of thecaas_admindatabase. They allow permissions to be defined for resources in multiple or all databases. -
Local API Keys
Local API Keys are defined per database and are managed in theapikeyscollection of any database. Unlike global API Keys, local AAPI Keys can only define permissions for resources within the same database.
When authenticating with an API Key, the CaaS platform always searches for local AAPI Keys first. If no matching key is found, global API Keys are then evaluated.
2.2.1. Authorization model
Authorization of an API Key is performed based on each permission that is part of the key. The list of permissions is defined in the permissions attribute of the key.
A permission has the following attributes:
-
url: specifies the path to which the permission applies (e.g., a collection or endpoint) -
permissionMode: determines the type of check, e.g., PREFIX, REGEX, or GRAPHQL -
methods: lists the allowed HTTP methods (e.g., GET, POST, PUT)
When checking a permission, the url attribute is used. Its value is compared to the URL path of an incoming request. The type of check depends on the permissionMode of the permission.
There are three different permission modes:
-
PREFIXandREGEX
InPREFIXmode, it is checked whether the value of theurlattribute is a prefix of the URL path of an incoming request.In
REGEXmode, a regular expression must be stored in theurlattribute. The check is then performed by matching whether the URL path of an incoming request matches the pattern of the regular expression.Furthermore, for the permission modes
PREFIXandREGEX, there is a fundamental distinction between the functionality of global and local API Keys. Global API Keys always check against the entire path of the request, while local API Keys check against the part of the path after the database. More information on global and local API Keys can be found in the example Difference between local and global API Keys or in the chapter API Keys. -
GRAPHQL
TheGRAPHQLmode allows permission to execute a GraphQL application. The value of theurlattribute is compared to the URI of a GraphQL application used by an incoming request. The URI of a GraphQL application is specified in thedescriptor.uriattribute of the app definition. Further details can be found in the chapter GraphQL.
The following table illustrates the difference in authorization between local and global API Keys when using the permission modes PREFIX or REGEX.
Permission in API Key (url attribute) |
Type of API Key | Request URL path | Access allowed |
|---|---|---|---|
|
/ |
global |
/ |
yes |
|
/project/ |
yes |
||
|
/project/content/ |
yes |
||
|
/other-project/ |
yes |
||
|
/other-project/content/ |
yes |
||
|
/project/ |
global |
/ |
no |
|
/project/ |
yes |
||
|
/project/content/ |
yes |
||
|
/other-project/ |
no |
||
|
/other-project/content/ |
no |
||
|
/ |
local in |
/ |
no |
|
/project/ |
yes |
||
|
/project/content/ |
yes |
||
|
/other-project/ |
no |
||
|
/other-project/content/ |
no |
||
|
/content/ |
local in |
/ |
no |
|
/project/ |
no |
||
|
/project/content/ |
yes |
||
|
/other-project/ |
no |
||
|
/other-project/content/ |
no |
2.2.2. Management via REST endpoints
The following endpoints are available for managing API Keys:
-
GET /<database>/apikeys
-
POST /<database>/apikeys
Note: The parameters_idandkeymust be provided and have identical values -
PUT /<database>/apikeys/{id}
Note: Thekeyparameter must have the same value as the{id}in the URL -
DELETE /<database>/apikeys/{id}
The database to use depends on the type of API Key, see chapter API Keys.
|
An API Key can also be used as an authorization method for managing API Keys. In this case, the API Key used must have write permissions on the corresponding API Keys collection. This also applies to read-only requests and serves to prevent privilege escalation. |
Example for creating a local API Key using curl:
curl "https://REST-HOST:PORT/<tenant>/apikeys" \
-H 'Content-Type: application/json' \
-u '<USER>:<PASSWORD>' \
-d $'{
"_id": "1e0909b7-c943-45a5-ae96-79f294249d48",
"key": "1e0909b7-c943-45a5-ae96-79f294249d48",
"name": "New-Apikey",
"description": "Some descriptive text",
"permissions": [
{
"url": "/<collection>",
"permissionMode": "PREFIX",
"methods": [
"GET",
"PUT",
"POST",
"PATCH",
"DELETE",
"HEAD",
"OPTIONS"
]
}
]
}'
The API Key shown in this example is of type "local" because it is defined in the apikeys collection under the database <tenant> and is therefore local to that database. The permission applies to all paths starting with /<tenant>/<collection>, since the permission mode PREFIX is used, and includes all HTTP methods listed in methods.
|
To create an API Key with REGEX mode, the above example must be adjusted as follows:
|
|
The |
2.2.3. Validation of API Keys
Every API Key is validated against a stored JSON schema when created and updated. The JSON schema ensures the basic structure of API Keys and can be queried at /<database>/_schemas/apikeys.
Further validations ensure that no two API Keys with the same key can be created. Also, an API Key may not contain a URL more than once.
If an API Key does not meet the requirements, the corresponding request is rejected with HTTP status 400.
If the JSON schema was not successfully stored in the database beforehand, requests are answered with HTTP status 500.
|
The |
2.3. Push Notifications (Change Streams)
It is often desirable to be informed about changes in the CaaS platform. For this purpose, the CaaS platform offers Change Streams. This feature allows you to establish a WebSocket connection to the CaaS platform, through which events about various changes are published.
Change Streams are created by storing a definition in the metadata of a collection. If you use CaaS Connect, some predefined Change Streams are already created for you. You can also define your own change streams.
The format of the events corresponds to the standard MongoDB events.
|
When working with WebSockets, consider possible connection interruptions. Regular |
|
You can find an example for using Change Streams in the browser in the Appendix. |
2.4. Additional Information
Further information about the functionality of the REST Interface can be found in the official RESTHeart documentation.
3. REST API
3.1. Querying Documents and Media
Content is stored in so-called collections, which belong to databases. The following three-part URL schema applies:
Queries are performed using the HTTP GET method.
For binary content, special collections (so-called buckets) are provided, which always end with the suffix .files. The result document for a medium does not contain the binary data, but links that point to the URL with the actual binary data. For more information on binary content, see the RESTHeart documentation.
|
Please note that binary content is not transferred to the CaaS buckets in our SaaS offering. |
If you use CaaS Connect, you can find more information in the CaaS Connect documentation.
3.1.1. Using Filters
Filters are used whenever documents should be retrieved based on their content rather than their ID. This allows both individual and multiple documents to be retrieved.
For example, the query for all English-language documents from the products collection has the following structure:
|
Beyond this example, there are further filtering options. More information can be found in the query documentation. |
|
Please note that the query parameter |
3.1.2. Format of Result Documents
By default, the interface returns all JSON data in HAL format. Thus, they are not just raw data, as is traditionally the case with unstructured JSON content.
The HAL format offers the advantage of simple yet powerful structuring. In addition to the required content, the results contain additional meta-information about the structure of this content.
Example
{ "_size": 5,
"_total_pages": 1,
"_returned": 3,
"_embedded": { CONTENT }
}
In this example, a filtered query was executed. Without knowing the exact content, its structure can be read directly from the meta-information. The REST Interface returns three results matching the filter criteria from a set of five documents and presents them on a single page.
|
If the requested element is a medium, the URL only retrieves its metadata. |
3.1.3. Page Size of Queries
The results of the REST Interface are always paginated to avoid performance issues with large collections. To control the requested page and the number of documents per page, the HTTP query parameters page and pagesize can be used in GET requests.
The default value for the pagesize parameter is set to 20 in the CaaS platform, and the maximum is 100.
These values can be changed for on-premises installations (see Operations Guide).
More information can be found in the RESTHeart documentation.
3.1.4. Resolving References
CaaS documents can reference other CaaS documents. When processing documents, the referenced content is often needed directly. To avoid sequential queries in these cases, the query parameter resolveRef can be used.
The following two JSON documents illustrate this:
{
"_id": "my-document",
"fsType": "ProjectProperties",
"formData": {
"ps_audio": {
"fsType": "FS_REFERENCE",
"value": {
"fsType": "PageRef",
"url": "https://REST-HOST:PORT/my-db/col/my-referenced-document"
}
}
}
}
{
"_id": "my-referenced-document",
"fsType": "PageRef",
"name": "audio"
}
In the first document, the JSON under formData.ps_audio.value.url contains an absolute URL to another document in the CaaS. The following request shows how this reference is resolved in the same request that reads the document.
curl -X GET --location "https://REST-HOST:PORT/my-db/col/my-document?resolveRef=formData.ps_audio.value.url" \
-H "Authorization: Bearer my-api-key"
The value of the query parameter must be the URL path specified in the JSON. The response then contains an additional attribute _resolvedRefs:
{
"_id": "my-document",
"fsType": "ProjectProperties",
"formData": {
"ps_audio": {
"fsType": "FS_REFERENCE",
"value": {
"fsType": "PageRef",
"url": "https://REST-HOST:PORT/my-db/col/my-referenced-document"
}
}
},
"_resolvedRefs": {
"https://REST-HOST:PORT/my-db/col/my-referenced-document": {
"_id": "my-referenced-document",
"fsType": "PageRef",
"name": "audio"
}
}
}
|
Reference resolution is affected by Configuration and limitations. |
The resolveRef parameter is also supported for collection queries. In this case, references in all returned documents are resolved. The documents of the resolved references are collected in a new, additional document, which is then added to the response array.
curl -X GET --location "https://REST-HOST:PORT/my-db/col?resolveRef=formData.ps_audio.value.url" \
-H "Authorization: Bearer my-api-key"
[
{
"_id": "my-document",
"fsType": "ProjectProperties",
"formData": {
"ps_audio": {
"fsType": "FS_REFERENCE",
"value": {
"fsType": "PageRef",
"url": "https://REST-HOST:PORT/my-db/col/my-referenced-document"
}
}
}
},
{
"_id": "_resolvedRefs",
"https://REST-HOST:PORT/my-db/col/my-referenced-document": {
"_id": "my-referenced-document",
"fsType": "PageRef",
"name": "audio"
}
}
]
|
This additional document is always the last element in the response array and can be identified by the ID |
|
The |
Transitive References
Referenced documents may themselves contain further references. These can also be resolved in the original request. For this, the path to the next reference must also be specified in the request, including the prefix $i., where i is the depth of the reference resolution chain.
The following request shows this based on the previous example, using a reference in the attribute page.url of the document my-referenced-document:
curl -X GET --location "https://REST-HOST:PORT/my-db/col?resolveRef=formData.ps_audio.value.url&resolveRef=$1.page.url" \
-H "Authorization: Bearer my-api-key"
Reference Path Syntax
Depth 0 describes the documents that are returned for the original request. The prefix $0. is optional for depth 0. Depth 1+ means all documents that were found by successfully resolving the references at the previous depth.
| Depth | JSON document | resolveRef | Explanation |
|---|---|---|---|
|
0 |
|
|
In all documents of depth 0, references are searched for under the path |
|
|
|||
|
|
The array with the name |
|
|
|
Resolves |
||
|
|
Resolves |
||
|
|
All arrays in the |
|
|
|
The first array in the |
||
|
1 |
<JSON document> |
|
The paths must start with |
|
n |
<JSON document> |
|
The paths must start with |
Configuration and limitations
-
Only absolute URLs can be resolved.
-
No errors are thrown when resolving references if incorrect paths or URLs are specified. Incorrect paths and references are silently ignored.
-
Paths must be specified according to the Reference Path Syntax. The syntax is based on JsonPath, but only the documented operators are supported.
-
URLs in the
_resolvedRefsdocument are not normalized. URL references that are not exactly identical are considered different documents, even if they point to the same document in the CaaS. This can be caused, for example, by an additional/at the end of the URL or by different query parameters. -
By default, the maximum depth for reference resolution is 3. Thus, a maximum of
$2.can be used as a path prefix for references. -
A maximum of 100 different references can be resolved in a single request. Once this limit is reached, no further references are collected and therefore not part of the response.
-
Reference resolution is enabled by default.
-
The settings for maximum depth and reference limit, as well as reference resolution itself, can be changed in an on-premises installation; see Operations Guide.
3.1.5. Indexes for Efficient Query Execution
The runtime of queries with filters (see chapter Use of Filters) can increase as the number of documents in a collection grows. If it exceeds a certain value, the query will be answered by the REST Interface with HTTP status 408. More efficient execution can be achieved by creating an index on the attributes used in the relevant filter queries.
Detailed information about database indexes can be found in the documentation of MongoDB.
- Predefined Indexes
-
If you are using CaaS Connect, predefined indexes are already created to support some common filter queries.
The exact definitions can be retrieved at https:/REST-HOST:PORT/<database>/<collection>/_indexes/.
- Custom Indexes
-
If the predefined indexes do not cover your use cases and you observe long response times or even request timeouts during queries, you can create your own indexes. The REST Interface can be used to manage the desired indexes. The procedure is described in the RESTHeart documentation.
|
Please only create the indexes you actually need. |
3.2. Modifying and Deleting Documents
To modify documents, you can use the HTTP methods POST, PUT, and PATCH. Additionally, documents can be deleted using the DELETE verb.
The following example shows how to create a document my-document within the collection my-collection in the database my-db.
curl --location --request PUT 'https://REST-HOST:PORT/my-db/my-collection/my-document' \
--header 'Authorization: Bearer my-api-key' \
--header 'Content-Type: application/json' \
--data-raw '{
"data": "some-data"
}'
For more information on storing documents, refer to the corresponding sections in the RESTHeart documentation.
|
When storing documents using the POST, PUT, or PATCH verbs, the default write mode is |
3.3. Managing Databases and Collections
Unlike document storage, managing databases and collections is limited to the HTTP methods PUT and DELETE.
The following example shows how to create the database my-db with a PUT request.
curl --location --request PUT 'https://REST-HOST:PORT/my-db' \
--header 'Authorization: Bearer my-api-key'
|
There are reserved databases that cannot be used to store regular content. These include |
For more information on managing databases, refer to the corresponding sections in the RESTHeart documentation.
|
Managing databases is not supported in our SaaS offering due to restricted access permissions. |
A collection my-collection can be created in the database my-db with a PUT request as follows.
curl --location --request PUT 'https://REST-HOST:PORT/my-db/my-collection' \
--header 'Authorization: Bearer my-api-key'
|
There are reserved collections that cannot be used to store regular content. Reserved collection names include |
For more information on managing collections, refer to the corresponding sections in the RESTHeart documentation.
4. GraphQL
Unlike classic REST queries, GraphQL offers a flexible and typed interface that can retrieve complex, nested data structures in a single request. This simplifies frontend development, as fewer roundtrips and individual endpoints are needed. Additionally, GraphQL enables a clear definition of the data model and better documentation of available queries.
|
Mutations are currently not supported. |
4.1. Authentication and Authorization
The authentication and authorization process for GraphQL queries works differently than for REST API queries. Unlike REST API queries, where the request path of the query is checked against a list of allowed URLs, GraphQL queries are authorized using one of the following access checks:
-
Explicit execution permission
Checks whether the API Key has explicit permission (GRAPHQLpermission of the API Key) to send queries to the GraphQL application. -
Implicit execution permission
Checks whether the API Key has access to all databases and collections. This is checked by matching the URL permissions (PREFIXandREGEX) of the API Key with the paths of all databases and collections that the GraphQL application allows access to.
|
For local API Keys, there is an additional condition that they may only access GraphQL applications of the same database. |
For more information on the different types of permissions of a API Key, see chapter Authorization model.
4.2. Managing GraphQL applications
With REST Interface, you can create, update, and delete your own GraphQL applications. If you are using CaaS Connect, predefined applications are already created that are specifically tailored to querying FirstSpirit content.
A GraphQL application has an app definition that must be stored in the gql-apps collection. This collection is automatically created when a database is created/updated. However, if it does not yet exist in the database, it must first be created manually with a PUT request.
|
The |
When editing the GraphQL app definition (e.g., Creating/Updating an Application), the permissions of the API Key are checked. An operation can only be performed if the API Key has access to all databases and collections listed in the definition.
|
A quick start guide can be found in the appendix under Quickstart: GraphQL applications. |
4.2.1. Creating/Updating an Application
To create or update a GraphQL application, send a PUT request with the app definition to the following URL:
https://REST-HOST:PORT/<tenant>/gql-apps/<app-uri>
Notes:
-
<app-uri> must have the value <tenant>___<my-appname>.
-
The
descriptor.uriparameter of the app definition must match <app-uri> and, unlike the RESTHeart documentation, is not optional in CaaS.
This provisions an endpoint at https://REST-HOST:PORT/graphql/<app-uri>, which can then be used for GraphQL queries. Information about executing GraphQL queries can be found in chapter GraphQL API.
All operations on GraphQL applications are automatically mirrored in the /caas_admin/gql-apps collection for technical reasons. See chapter Resynchronizing existing GraphQL applications for information on how this mechanism can also be triggered manually if needed.
Providing the App Definition
There are two ways to provide the app definition in the PUT request:
-
Using the JSON body
The complete app definition is sent in the request body (Content-Typeapplication/json).An example of such a GraphQL app definition can be found in chapter GraphQL example application.
-
Using file upload
Instead of sending the entire app definition, it can also be transmitted as a multipart upload (Content-Typemultipart/form-data).
The components of the app definition are split into two separate files, which must be sent as individual file parts in the request:
Partapp: File containing thedescriptorandmappingsections in JSON format.
Partschema: File containing the raw text version ofschemain the GraphQL schema definition language.Uploading the files usingcurlcurl -i -X PUT \ -H "Authorization: Bearer $API_KEY" \ -F app=@my-app-def.json \ -F schema=@my-schema.gql \ https://REST-HOST:PORT/<tenant>/gql-apps/<tenant>___<name>For fast feedback cycles during development, this can be combined with a file change monitoring tool to continuously update the GraphQL application:
Continuous upload of files usingfswatchandcurlfswatch -o my-app-def.json my-schema.gql | xargs -n1 -I{} \ curl -i -X PUT \ -H "Authorization: Bearer $API_KEY" \ -F app=@my-app-def.json \ -F schema=@my-schema.gql \ https://REST-HOST:PORT/<tenant>/gql-apps/<tenant>___<name>
Notes
-
A GraphQL application can query data using a MongoDB query or aggregation. The chapter Object Mappings in the RESTHeart documentation contains more detailed information. An example of a GraphQL application that uses an aggregation can be found in the appendix.
Variables within authorization-relevant attributes of aggregation stages (such as database or collection names) are not permitted.
-
Complex mappings with multiple foreign key relationships can result in increased response times for queries. For more efficient query execution, we recommend using indexes. Configuring batching and caching can also help optimize response times. For details, see this documentation.
4.2.2. Deleting the application
To delete a GraphQL application, a DELETE request is sent to the following URL:
https://REST-HOST:PORT/<tenant>/gql-apps/<tenant>___<name>
4.2.3. Resynchronizing existing GraphQL applications
Under certain conditions, such as after restoring individual collections from a backup, it may happen that previously created GraphQL applications are no longer synchronized. In such cases, all tenant-specific GraphQL applications must be resynchronized.
To resynchronize all existing GraphQL applications of all tenants, a POST request must be made to the HTTP endpoint /_logic/sync-gql-apps.
4.3. GraphQL API
Each of the GraphQL applications defined via the management API (see GraphQL) provides a GraphQL API endpoint. This endpoint can be used to retrieve data (see Fetching data).
4.3.1. Fetching data
The GraphQL API can be queried via the following HTTP endpoints:
https://REST-HOST:PORT/graphql/<app-uri>
To query data, send a POST request to the desired endpoint and specify the query with JSON in the request body, e.g.:
curl
curl -i -X POST \
-H “Content-Type: application/json” \
-H “Authorization: Bearer $API_KEY” \
-d '{“query”: "query ($lang: [String!]){products(_language: $lang) {name description categories {name} picture {name binaryUrl width height}}}“, ”variables“: {‘lang’: [”EN"]}}' \
https://REST-HOST:PORT/graphql/<app-uri>
|
A more detailed example of how to use GraphQL can be found in the appendix. |
|
Similar to page size limits for REST queries, there is also a limit on the number of results for GraphQL queries. The default value is 20 documents. Please use pagination arguments if more documents need to be queried. Further information can be found in the |
4.3.2. Model Context Protocol (MCP) Server for AI (Alpha)
We offer an experimental MCP server that helps AI agents interact better with the FirstSpirit GraphQL API. It helps AI create valid GraphQL queries and check their functionality. For more information, see the CaaS MCP Server documentation and the FirstSpirit GraphQL API documentation.
5. Appendix
5.1. Examples
5.1.1. Change stream example
<script type="module">
import PersistentWebSocket from 'https://cdn.jsdelivr.net/npm/pws@5/dist/index.esm.min.js';
// Replace this with your API key (needs read access for the preview collection)
const apiKey = "your-api-key";
// Replace this with your preview collection url (if not known copy from CaaS Connect Project App)
// e.g. "https://REST-HOST:PORT/my-tenant-id/f948bb48-4f6b-4a8a-b521-338c9d352f2b.preview.content"
const previewCollectionUrl = new URL("your-preview-collection-url");
const pathSegments = previewCollectionUrl.pathname.split("/");
if (pathSegments.length !== 3) {
throw new Error(`The format of the provided url '${previewCollectionUrl}' is incorrect and should only contain two path segments`);
}
(async function(){
// Retrieving temporary auth token
const token = await fetch(new URL(`_logic/securetoken?tenant=${pathSegments[1]}`, previewCollectionUrl.origin).href, {
headers: {'Authorization': `Bearer ${apiKey}`}
}).then((response) => response.json()).then((token) => token.securetoken).catch(console.error);
// Establishing WebSocket connection to the change stream "crud"
// ("crud" is the default change stream that the CaaS Connect module provides)
const wsUrl = `wss://${previewCollectionUrl.host + previewCollectionUrl.pathname}`
+ `/_streams/crud?securetoken=${token}`;
const pws = new PersistentWebSocket(wsUrl, { pingTimeout: 60000 });
// Handling change events
pws.onmessage = event => {
const {
documentKey: {_id: documentId},
operationType: changeType,
} = JSON.parse(event.data);
console.log(`Received event for '${documentId}' with change type '${changeType}'`);
}
})();
</script>
5.1.2. GraphQL example application
This chapter describes a use case for a GraphQL application as an example. For this we will outline the individual steps that belong to the creation of a GraphQL application and its later use.
Create the GraphQL app definition
In the example scenario, a GraphQL application is created that can be used to query data records located in the CaaS. The data sets used here are the products from the example project of the fictitious company “Smart Living”. Image references and product categories in the data sets are resolved directly.
The entire command to create the GraphQL app definition for this example scenario looks like this.
curl --location --request PUT 'https://REST-HOST:PORT/mycorp-dev/gql-apps/mycorp-dev___products' \
--header 'Authorization: Bearer <PERMITTED_APIKEY>' \
--header 'Content-Type: application/json' \
--data-raw '{
"descriptor": {
"name": "products",
"description": "example app to fetch product relevant information from SLG",
"enabled": true,
"uri": "mycorp-dev___products"
},
"schema": "type Picture{ name: String! identifier: String! binaryUrl: String! width: Int! height: Int! } type Category{ name: String! identifier: String! } type Product{ name: String! identifier: String! description: String categories: [Category] picture: Picture } type Query{ products(_language: [String!] = [\"DE\", \"EN\"]): [Product] }",
"mappings": {
"Category": {
"name": "displayName",
"identifier": "_id"
},
"Picture": {
"name": "displayName",
"identifier": "_id",
"binaryUrl": "resolutionsMetaData.ORIGINAL.url",
"width": "resolutionsMetaData.ORIGINAL.width",
"height": "resolutionsMetaData.ORIGINAL.height"
},
"Product": {
"name": "displayName",
"identifier": "_id",
"description": "formData.tt_abstract.value",
"picture": {
"db": "mycorp-dev",
"collection": "d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content",
"find": {
"identifier": {
"$fk": "formData.tt_media.value.0.formData.st_media.value.identifier"
},
"locale.identifier": {
"$fk": "locale.identifier"
}
}
},
"categories": {
"db": "mycorp-dev",
"collection": "d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content",
"find": {
"identifier": {
"$in": {
"$fk": "formData.tt_categories.value.identifier"
}
},
"locale.identifier": {
"$fk": "locale.identifier"
}
}
}
},
"Query": {
"products": {
"db": "mycorp-dev",
"collection": "d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content",
"find": {
"locale.identifier": { "$in": { "$arg": "_language" } },
"entityType": "product"
}
}
}
}
}'
|
When creating a GraphQL app definition, the schema must be specified as a JSON string. For better readability, we recommend formatting the schema more appropriately. |
Schema of the GraphQL app definition
The schema used for the example contains the following definitions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Picture {
name: String!
identifier: String!
binaryUrl: String!
width: Int!
height: Int!
}
type Category {
name: String!
identifier: String!
}
type Product {
name: String!
identifier: String!
description: String
categories: [Category]
picture: Picture
}
type Query {
products(_language: [String!] = ["DE", "EN"]): [Product]
}
Lines 1, 9 and 14 of the schema are the starting point for the type definitions of the objects used in the GraphQL app. In addition, each GraphQL schema contains a query type (line 22) that defines what data can be queried by a GraphQL app. More details about schemas in GraphQL can be found in the GraphQL documentation.
In line 23 we define a query with the name products, which returns a collection of [Product]. To specify the languages in which we need this data, we add the _language variable. As most of our customers are German or English, we also add a default value of ["DE", "EN"]. This marks the variable as optional.
Mapping the GraphQL app definition
The GraphQL app definition mapping represents the connection between the schema and the data in the database. Each type described in the schema generally requires an explicit entry, so this part of a GraphQL app definition is usually the longest. There may be situations where the fields in the type should be named exactly as the keys of the data. In this special case, no explicit entry in the mapping is necessary. For details about the mapping in GraphQL app definition, see the corresponding chapter in RESTHeart documentation.
The following example is an excerpt from creating a GraphQL app definition and clarifies some use cases.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
{
"Category": {
"name": "displayName",
"identifier": "_id"
},
"Picture": {
"name": "displayName",
"identifier": "_id",
"binaryUrl": "resolutionsMetaData.ORIGINAL.url",
"width": "resolutionsMetaData.ORIGINAL.width",
"height": "resolutionsMetaData.ORIGINAL.height"
},
"Product": {
"name": "displayName",
"identifier": "_id",
"description": "formData.tt_abstract.value",
"picture": {
"db": "mycorp-dev",
"collection": "d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content",
"find": {
"identifier": {
"$fk": "formData.tt_media.value.0.formData.st_media.value.identifier"
},
"locale.identifier": {
"$fk": "locale.identifier"
}
}
},
"categories": {
"db": "mycorp-dev",
"collection": "d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content",
"find": {
"identifier": {
"$in": {
"$fk": "formData.tt_categories.value.identifier"
}
},
"locale.identifier": {
"$fk": "locale.identifier"
}
}
}
},
"Query": {
"products": {
"db": "mycorp-dev",
"collection": "d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content",
"find": {
"locale.identifier": {
"$in": {
"$arg": "_language"
}
},
"entityType": "product"
}
}
}
}
The first use case considered is the so-called field to field mapping. In this type of mapping, a field in the type is assigned a corresponding attribute of the data. An example of this can be seen in line 3, where the field Category.name from the schema refers to attribute displayName from the data.
The second use case is the field to query mapping. Here a field in the type is mapped to the result of a data query. An example of such a mapping can be found in line 45ff: the field Query.products is mapped by the data found in the REST Interface under /mycorp-dev/d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content and correspond to the filters entityType": "product" and "locale.identifier": { "$in": { "$arg":"_language" } }. This means that exactly those products are queried which are located in the defined source, represent an entity of “product” and use one of the language abbreviations passed in the “_language” argument.
Another example of a “field to query mapping” can be found starting at line 29. In this mapping definition, the product categories, which are maintained in separate records, are identified using a foreign key relationship. The complete entry from line 29-42 states that the product.categories field will list all product categories that are under /mycorp-dev/d8db6f24-0bf8-4f48-be47-5e41d8d427fd.preview.content, whose identifier is stored in the formData.tt_categories.value.identifier field, and whose locale.identifier exactly matches what is in the product record as locale.identifier. Since a product can reference multiple categories in the dataset under formData.tt_categories.value.identifier, the key $in is used here.
|
If multiple filters are specified in a |
Using the GraphQL app
Requests can now be made to a GraphQL application using this app definition.
curl --location --request POST 'https://REST-HOST:PORT/graphql/mycorp-dev___products' \
--header 'Authorization: Bearer <PERMITTED_APIKEY>' \
--header 'Content-Type: application/json' \
--data-raw '{"query": "query($lang: [String!]){products(_language: $lang) {name description categories {name} picture {name binaryUrl width height}}}", "variables": {"lang": ["DE"]}}'
This request example shows how to call the GraphQL app using curl. The app is always available at /graphql/<descriptor.uri>. Through this query, product data is retrieved depending on the variable $lang. The variable is passed as a value to the _language argument defined in the schema. Since a default value for _language is included in the schema, specifying a value is optional in this scenario. Further details on query arguments and variables can be found in the GraphQL documentation.
5.1.3. GraphQL example application using Aggregations
Aggregations can be used to create complex queries to the CaaS. For example, it’s possible to dynamically add a computed attribute to the returned documents. Much more complex aggregations can be created. A full list of possible aggregation stages and operators can be found here.
{
"_id": "mytenantid-dev___pagerefs",
"descriptor": {
"name": "pagerefs",
"description": "Query PageRefs",
"enabled": true,
"uri": "mytenantid-dev___pagerefs"
},
"schema": "type PageRef { _id: String projectId: String } type Query{ pageRefs(projectId: String): [PageRef] }",
"mappings": {
"PageRefs": {
"count": "count"
},
"Query": {
"pageRefs":{
"db": "mytenantid-dev",
"collection": "641154a9-b90c-4b10-a5f7-38677cbb5abc.release.content",
"stages": [
{ "$match": { "fsType":"PageRef" }},
{ "$addFields": { "projectId": { "$arg": "projectId" } } }
]
}
}
}
}
5.2. Tutorials
5.2.1. Getting started with GraphQL Apps
This tutorial serves as an introduction to creating and using GraphQL apps. To complete it you need to be familiar with the REST Interface in terms of authentication and querying documents.
GraphQL apps allow you to query your CaaS documents via your own GraphQL API endpoints. These endpoints also make the document data accessible in a specific format/schema that you define.
In the following steps we will be creating such a GraphQL app and use it to query document data. We’ll be using curl to send HTTP requests, but you can use other alternative tools.
-
Create sample documents
Let’s create a collection to store some sample documents.
# set these once so you can re-use them for other commands TENANT='YOUR-TENANT-ID' API_KEY='YOUR-API-KEY' curl --location --request PUT "https://REST-HOST:PORT/$TENANT/posts" \ --header "Authorization: Bearer $API_KEY"And create the documents.
# you can execute this multiple times to create many documents curl --location "https://REST-HOST:PORT/$TENANT/posts" \ --header "Authorization: Bearer $API_KEY" --header 'Content-Type: application/json' \ --data "{ \"content\": \"My post created at $(date)..\" }" -
Define the desired GraphQL schema
Now that we have documents available, we need to define a schema for accessing their data.
We’ll save a simple data model for the sample documents we created earlier, but you can create arbitrarily complex data models.
Save GraphQL schema definition (schema.gql)cat > schema.gql << EOF type BlogPost { content: String! } type Query { posts: [BlogPost!] } EOF -
Create the GraphQL API endpoint
The next step is to create the GraphQL app using our schema, which automatically provisions a new API endpoint.
First, however, we need define a couple of things so that CaaS knows how to provision our new endpoint and how to fetch/map the documents to our schema. We do this by creating a GraphQL app definition.
Save GraphQL app definition (app.json)cat > app.json << EOF { "descriptor": { "name": "myposts", "description": "Example app to fetch blog posts.", "enabled": true, "uri": "${TENANT}___myposts" }, "mappings": { "Query": { "posts": { "db": "$TENANT", "collection": "posts", "find": { "content": { "\$exists": true } } } } } } EOFNow that we have prepared a schema (
schema.gql) and a corresponding app definition (app.json), we can use them both to create a GraphQL app using the REST Interface.Creating GraphQL appcurl -X PUT \ -H "Authorization: Bearer $API_KEY" \ -F app=@app.json \ -F schema=@schema.gql \ https://REST-HOST:PORT/$TENANT/gql-apps/${TENANT}___myposts -
Query data using the new endpoint
At this point, CaaS has automatically provisioned a new GraphQL API endpoint using our definitions. The endpoint is available at
https://REST-HOST:PORT/graphql/\{YOUR-TENANT-ID}___mypostsand we can query our documents using the new endpoint.
curl --location "https://REST-HOST:PORT/graphql/${TENANT}___myposts" \ --header "Authorization: Bearer $API_KEY" \ --header 'Content-Type: application/json' \ --data '{"query":"{ posts { content } }","variables":{}}' -
Congratulations on querying data using your own GraphQL app! You should see a result similar to this.
{ "data": { "posts": [ { "content": "My post created at Tue Aug 8 17:08:32 CEST 2023.." }, { "content": "My post created at Tue Aug 8 17:12:23 CEST 2023.." } ] } }
|
The default page size for GraphQL queries is 20. If you need to query more documents than that, please use pagination arguments. For more information see section |
6. Help
The Technical Support of the Crownpeak Technology GmbH provides expert technical support covering any topic related to the FirstSpirit™ product. You can get and find more help concerning relevant topics in our community.
7. Disclaimer
This document is provided for information purposes only. Crownpeak Technology GmbH may change the contents hereof without notice. This document is not warranted to be error-free, nor subject to any other warranties or conditions, whether expressed orally or implied in law, including implied warranties and conditions of merchantability or fitness for a particular purpose. Crownpeak Technology GmbH specifically disclaims any liability with respect to this document and no contractual obligations are formed either directly or indirectly by this document. The technologies, functionality, services, and processes described herein are subject to change without notice.