GraphQL

Getting started

personClever Llamas
calendar_today2023-01-25

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!