GraphQL
Getting started
The GraphQL implementation is built into MarkLogic 11 and is available without any additional installation required. It relies on the same schemas and views that form the base of MarkLogic’s Optic and SQL services.
Due to its built-in implementation, getting started with GraphQL takes almost no effort.
The setup
This walkthrough is based on a clean MarkLogic installation and all scripts are executed as admin
against the Documents
database unless stated otherwise.
Create views
We start by inserting some simple TDE templates, one for our llamas, one for the farms and another for each farm's pastures. The views will be used as the basis for the GraphQL type definitions which we'll explore later.
'use strict';
declareUpdate();
var tde = require("/MarkLogic/tde.xqy");
var template = {
template: {
context: "/features[fn:lower-case(./properties/type) = 'llama']",
collections: [{ collectionsAnd: ["/type/llamaverse"] }],
rows: [
{
schemaName: "llamaverse",
viewName: "llamas",
columns: [
{
name: "id",
scalarType: "int",
val: "./properties/id",
},
{
name: "name",
scalarType: "string",
val: "./properties/name",
},
{
name: "type",
scalarType: "string",
val: "./properties/type",
},
{
name: "birthDate",
scalarType: "date",
val: "./properties/birthDate",
},
{
name: "pastureId",
scalarType: "int",
val: "./properties/pasture",
},
],
},
],
},
};
tde.templateInsert("/llamaverse/llamas.json", template);
'use strict';
declareUpdate();
var tde = require("/MarkLogic/tde.xqy");
var template = {
template: {
context: "/features[fn:lower-case(./properties/type) = 'pasture']",
collections: [{ collectionsAnd: ["/type/llamaverse"] }],
rows: [
{
schemaName: "llamaverse",
viewName: "pastures",
columns: [
{
name: "id",
scalarType: "int",
val: "./properties/id",
},
{
name: "name",
scalarType: "string",
val: "./properties/name",
},
{
name: "type",
scalarType: "string",
val: "./properties/type",
},
{
name: "farmId",
scalarType: "int",
val: "./properties/farm",
},
],
},
],
},
};
tde.templateInsert("/llamaverse/pastures.json", template);
'use strict';
declareUpdate();
var tde = require("/MarkLogic/tde.xqy");
var template = {
template: {
context: "/features[fn:lower-case(./properties/type) = 'farm']",
collections: [{ collectionsAnd: ["/type/llamaverse"] }],
rows: [
{
schemaName: "llamaverse",
viewName: "farms",
columns: [
{
name: "id",
scalarType: "int",
val: "./properties/id",
},
{
name: "name",
scalarType: "string",
val: "./properties/name",
},
{
name: "type",
scalarType: "string",
val: "./properties/type",
},
],
},
],
},
};
tde.templateInsert("/llamaverse/farms.json", template);
'use strict';
declareUpdate();
const llamaverse = {
creator: "CleverLlamas",
name: "Llamaverse",
features: [
{
type: "Feature",
properties: {
id: 2,
name: "High Hill Field",
type: "pasture",
farm: 1,
},
},
{
type: "Feature",
properties: {
id: 3,
name: "Blackwater Pasture",
type: "pasture",
farm: 2,
},
},
{
type: "Feature",
properties: {
id: 1,
name: "Valley Pasture",
type: "pasture",
farm: 3,
},
},
{
type: "Feature",
properties: {
id: 4,
name: "Northern Fields",
type: "pasture",
farm: 3,
},
},
{
type: "Feature",
properties: {
id: 1,
name: "High Hill Farm",
type: "farm",
},
},
{
type: "Feature",
properties: {
id: 2,
name: "Blackwater Ranch",
type: "farm",
},
},
{
type: "Feature",
properties: {
id: 3,
name: "Llama Valley Estate",
type: "farm",
},
},
{
type: "Feature",
properties: {
id: 1,
name: "George",
type: "llama",
birthDate: "2007-06-01",
farm: 3,
pasture: 4,
},
},
{
type: "Feature",
properties: {
id: 2,
name: "Charlie",
type: "llama",
birthDate: "2002-06-10",
farm: 3,
pasture: 4,
},
},
{
type: "Feature",
properties: {
id: 3,
name: "Winnie",
type: "llama",
birthDate: "1998-11-22",
farm: 3,
pasture: 4,
},
},
{
type: "Feature",
properties: {
id: 4,
name: "Loretta",
type: "llama",
birthDate: "1996-11-14",
farm: 3,
pasture: 4,
},
},
{
type: "Feature",
properties: {
id: 5,
name: "Pablo",
type: "llama",
birthDate: "2003-11-11",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 6,
name: "Maria",
type: "llama",
birthDate: "2003-05-26",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 7,
name: "Jeff",
type: "llama",
birthDate: "2003-05-11",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 8,
name: "Tiff",
type: "llama",
birthDate: "2002-03-20",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 9,
name: "Larry",
type: "llama",
birthDate: "2005-10-02",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 10,
name: "Duke",
type: "llama",
birthDate: "2005-09-28",
farm: 2,
pasture: 3,
},
},
{
type: "Feature",
properties: {
id: 11,
name: "Elle",
type: "llama",
birthDate: "2005-03-24",
farm: 2,
pasture: 3,
},
},
{
type: "Feature",
properties: {
id: 12,
name: "Chip",
type: "llama",
birthDate: "1999-04-12",
farm: 2,
pasture: 3,
},
},
{
type: "Feature",
properties: {
id: 13,
name: "Beth",
type: "llama",
birthDate: "1999-10-19",
farm: 2,
pasture: 3,
},
},
{
type: "Feature",
properties: {
id: 14,
name: "Ruby",
type: "llama",
birthDate: "2000-06-04",
farm: 2,
pasture: 3,
},
},
{
type: "Feature",
properties: {
id: 15,
name: "James",
type: "llama",
birthDate: "2000-07-01",
farm: 3,
pasture: 1,
},
},
{
type: "Feature",
properties: {
id: 16,
name: "William",
type: "llama",
birthDate: "2002-08-15",
farm: 3,
pasture: 1,
},
},
{
type: "Feature",
properties: {
id: 17,
name: "Haley",
type: "llama",
birthDate: "2003-06-22",
farm: 3,
pasture: 4,
},
},
{
type: "Feature",
properties: {
id: 18,
name: "Ben",
type: "llama",
birthDate: "2004-01-22",
farm: 3,
pasture: 4,
},
},
{
type: "Feature",
properties: {
id: 19,
name: "Adam",
type: "llama",
birthDate: "2004-12-10",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 20,
name: "Jenny",
type: "llama",
birthDate: "2000-08-11",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 21,
name: "Mick",
type: "llama",
birthDate: "2005-02-06",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 22,
name: "Lizzy",
type: "llama",
birthDate: "2005-03-22",
farm: 1,
pasture: 2,
},
},
],
};
xdmp.documentInsert("/llamaverse.json", llamaverse, {
collections: ["/type/llamaverse"],
});
Load llamaverse
With the templates in place it is time to load the (partial) llamaverse. For this write-up we're going to insert a single JSON file that includes our llamas, pastures, and farms.
'use strict';
declareUpdate();
const llamaverse = {
creator: "CleverLlamas",
name: "Llamaverse",
features: [
{
type: "Feature",
properties: {
id: 2,
name: "High Hill Field",
type: "pasture",
farm: 1,
},
},
{
type: "Feature",
properties: {
id: 3,
name: "Blackwater Pasture",
type: "pasture",
farm: 2,
},
},
{
type: "Feature",
properties: {
id: 1,
name: "Valley Pasture",
type: "pasture",
farm: 3,
},
},
{
type: "Feature",
properties: {
id: 4,
name: "Northern Fields",
type: "pasture",
farm: 3,
},
},
{
type: "Feature",
properties: {
id: 1,
name: "High Hill Farm",
type: "farm",
},
},
{
type: "Feature",
properties: {
id: 2,
name: "Blackwater Ranch",
type: "farm",
},
},
{
type: "Feature",
properties: {
id: 3,
name: "Llama Valley Estate",
type: "farm",
},
},
{
type: "Feature",
properties: {
id: 1,
name: "George",
type: "llama",
birthDate: "2007-06-01",
farm: 3,
pasture: 4,
},
},
{
type: "Feature",
properties: {
id: 2,
name: "Charlie",
type: "llama",
birthDate: "2002-06-10",
farm: 3,
pasture: 4,
},
},
{
type: "Feature",
properties: {
id: 3,
name: "Winnie",
type: "llama",
birthDate: "1998-11-22",
farm: 3,
pasture: 4,
},
},
{
type: "Feature",
properties: {
id: 4,
name: "Loretta",
type: "llama",
birthDate: "1996-11-14",
farm: 3,
pasture: 4,
},
},
{
type: "Feature",
properties: {
id: 5,
name: "Pablo",
type: "llama",
birthDate: "2003-11-11",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 6,
name: "Maria",
type: "llama",
birthDate: "2003-05-26",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 7,
name: "Jeff",
type: "llama",
birthDate: "2003-05-11",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 8,
name: "Tiff",
type: "llama",
birthDate: "2002-03-20",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 9,
name: "Larry",
type: "llama",
birthDate: "2005-10-02",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 10,
name: "Duke",
type: "llama",
birthDate: "2005-09-28",
farm: 2,
pasture: 3,
},
},
{
type: "Feature",
properties: {
id: 11,
name: "Elle",
type: "llama",
birthDate: "2005-03-24",
farm: 2,
pasture: 3,
},
},
{
type: "Feature",
properties: {
id: 12,
name: "Chip",
type: "llama",
birthDate: "1999-04-12",
farm: 2,
pasture: 3,
},
},
{
type: "Feature",
properties: {
id: 13,
name: "Beth",
type: "llama",
birthDate: "1999-10-19",
farm: 2,
pasture: 3,
},
},
{
type: "Feature",
properties: {
id: 14,
name: "Ruby",
type: "llama",
birthDate: "2000-06-04",
farm: 2,
pasture: 3,
},
},
{
type: "Feature",
properties: {
id: 15,
name: "James",
type: "llama",
birthDate: "2000-07-01",
farm: 3,
pasture: 1,
},
},
{
type: "Feature",
properties: {
id: 16,
name: "William",
type: "llama",
birthDate: "2002-08-15",
farm: 3,
pasture: 1,
},
},
{
type: "Feature",
properties: {
id: 17,
name: "Haley",
type: "llama",
birthDate: "2003-06-22",
farm: 3,
pasture: 4,
},
},
{
type: "Feature",
properties: {
id: 18,
name: "Ben",
type: "llama",
birthDate: "2004-01-22",
farm: 3,
pasture: 4,
},
},
{
type: "Feature",
properties: {
id: 19,
name: "Adam",
type: "llama",
birthDate: "2004-12-10",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 20,
name: "Jenny",
type: "llama",
birthDate: "2000-08-11",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 21,
name: "Mick",
type: "llama",
birthDate: "2005-02-06",
farm: 1,
pasture: 2,
},
},
{
type: "Feature",
properties: {
id: 22,
name: "Lizzy",
type: "llama",
birthDate: "2005-03-22",
farm: 1,
pasture: 2,
},
},
],
};
xdmp.documentInsert("/llamaverse.json", llamaverse, {
collections: ["/type/llamaverse"],
});
Implicit schemas
Out of the box MarkLogic 11 automatically generates GraphQL type and query definitions based on your views. These generated definitions are known as implicit schemas
and are based on the TDE template configurations. This means that we can already write GraphQL queries against our views (or types) using these type and query definitions. By default query names are a concetenation of the schema name and the view name.
Query implicit schemas
A simple GraphQL query can be send to it using the browser:
http://localhost:8000/v1/rows/graphql?query={"query": "query Farms { llamaverse_farms { id name } }"}
You can also send the request using curl, Postman or any other tool of your choice:
curl --location --request POST 'http://localhost:8000/v1/rows/graphql' \
--header 'Content-Type: application/graphql' \
--header 'Authorization: Basic YWRtaW46YWRtaW4=' \
--data-raw '{"query":"query Farms { llamaverse_farms { id name } }\r\n","variables":{}}'
Regardless of the tooling used the query should return a list of farms from our llamaverse. Note that basic data types such as configured on our TDE templates are respected within the JSON response:
{
"data": {
"llamaverse_farms": [
{
"id": 3,
"name": "Llama Valley Estate"
},
{
"id": 2,
"name": "Blackwater Ranch"
},
{
"id": 1,
"name": "High Hill Farm"
}
]
},
"errors": []
}
Review implicit schemas
Implicit schemas are created automatically and cannot be altered other than by making changes to your TDE templates. The following code snippet allows you to review the generated schemas:
'use strict';
this.process = { env: { NODE_ENV: "development" } };
const {
print,
} = require("/MarkLogic/graphql/node_modules/graphql/language/printer");
const { getRequestSchema } = require("/MarkLogic/graphql/impl/mlGraphqlSchema");
print(getRequestSchema());
type llamaverse_llamas @View(schemaName: "llamaverse", viewName: "llamas") {
id: Int
name: String
type: String
birthDate: String
pastureId: Int
}
type llamaverse_pastures @View(schemaName: "llamaverse", viewName: "pastures") {
id: Int
name: String
type: String
farmId: Int
}
type llamaverse_farms @View(schemaName: "llamaverse", viewName: "farms") {
id: Int
name: String
type: String
}
type Query {
llamaverse_llamas(id: Int, name: String, type: String, birthDate: String, pastureId: Int): [llamaverse_llamas]
llamaverse_pastures(id: Int, name: String, type: String, farmId: Int): [llamaverse_pastures]
llamaverse_farms(id: Int, name: String, type: String): [llamaverse_farms]
}
Exploring GraphQL
With enough good material out there to get you started on GraphQL there is no need to elaborate on the general usage of GraphQL. However, there are a few custom directives that are unique to the implemenation of GraphQL with MarkLogic 11 which are highlighted below.
Ranges of values
Range queries are not part of the GraphQL specification but are commonly used with MarkLogic. The implementation of GraphQL on MarkLogic therefore provides several custom directives for providing range query capabilities:
- @greaterThan
- @greaterThanEqual
- @lessThan
- @lessThanEqual
In our example we're querying all llamas that are born from January 1st 1997 to January 1st 2001:
query {
llamaverse_llamas
@greaterThanEqual(birthDate: "1997-01-01")
@lessThan(birthDate: "2001-01-01")
{
name
birthDate
}
}
{
"data": {
"llamaverse_llamas": [
{
"name": "Winnie",
"birthDate": "1998-11-22"
},
{
"name": "Chip",
"birthDate": "1999-04-12"
},
{
"name": "James",
"birthDate": "2000-07-01"
},
{
"name": "Jenny",
"birthDate": "2000-08-11"
},
{
"name": "Ruby",
"birthDate": "2000-06-04"
},
{
"name": "Beth",
"birthDate": "1999-10-19"
}
]
},
"errors": []
}
Sorting and Pagination
The GraphQL specification does not describe sorting and is also handled by using a custom @Sort
directive provided with MarkLogic's GraphQL implementation The directive requires a field and direction as input:
query {
llamaverse_llamas
@greaterThanEqual(birthDate: "1997-01-01")
@lessThan(birthDate: "2001-01-01")
@Sort(field: birthDate, direction: Ascending)
{
name
birthDate
}
}
{
"data": {
"llamaverse_llamas": [
{
"name": "Winnie",
"birthDate": "1998-11-22"
},
{
"name": "Chip",
"birthDate": "1999-04-12"
},
{
"name": "Beth",
"birthDate": "1999-10-19"
},
{
"name": "Ruby",
"birthDate": "2000-06-04"
},
{
"name": "James",
"birthDate": "2000-07-01"
},
{
"name": "Jenny",
"birthDate": "2000-08-11"
}
]
},
"errors": []
}
NOTE
The MarkLogic 11.0.0 implementation does not currently allow for multi field sorts
The custom sort directive can be combined with the GraphQL arguments first
and offset
in order to perform pagination. The example below gets the set set of three llamas:
query {
llamaverse_llamas
(first: 3, offset: 3)
@greaterThanEqual(birthDate: "1997-01-01")
@lessThan(birthDate: "2001-01-01")
@Sort(field: birthDate, direction: Ascending)
{
name
birthDate
}
}
{
"data": {
"llamaverse_llamas": [
{
"name": "Ruby",
"birthDate": "2000-06-04"
},
{
"name": "James",
"birthDate": "2000-07-01"
},
{
"name": "Jenny",
"birthDate": "2000-08-11"
}
]
},
"errors": []
}
NOTE
Custom directives (using the "@" prefix) are required to come after non-directive arguments such as first
and offset
Conclusion
And that's how easy it is to get started with GraphQL using MarkLogic 11. Using the /v1/rows/graphql endpoint and the automatically generated implicit GraphQL schema's your data becomes available through another industry standard without any additional effort.
Although these out of the box schemas are a great starting point, the real power of GraphQL schemas on top of your data will be unleashed when we dive into explicit schemas
. By creating explicit schemas within your MarkLogic 11 server type definitions can be extended with relations allowing more complex GraphQL queries to be written allowing for rich graphs to be modelled.
More on explicit schemas in our next article.
Need Some Help?
Looking for more information on this subject or any other topic related to MarkLogic? Contact Us (info@cleverllamas.com) to find out how we can assist you with consulting or training!
- The setup
- Create views
- Load llamaverse
- Implicit schemas
- Query implicit schemas
- Review implicit schemas
- Exploring GraphQL
- Ranges of values
- Sorting and Pagination
- Conclusion