0 Comments

One very great feature is added to the Sitecore JSS module: GraphQL support. It allows you to query an API with a JSON payload. Compared to REST API's GraphQL is more maintainable and more flexible as GraphQL services can be stiched together.

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.

Building the Sitecore GraphQL SchemaProvider

In this post I will walk through the following steps to guide you setting up a custom GraphQL SchemaProvider from which you can query the enpoint.

Steps to create your own GraphQL schema in Sitecore:

  • Getting my initial information at Sitecore's JSS website
  • Setting up configuration in Sitecore
  • Create project with SchemaProvider, GraphType(s) and RootQuery
  • Adding Arguments
  • Using different FieldTypes
  • Query the custom API

The first step is to look at the example on the JSS website under the Techniques section. I have based my code on this example to keep it as simple as possible. When I wrote this post GraphQL was installed via installing JSS, but it should be available separately at launch (probably Oct. 2018).

First you need to set <compilation debug="true"> in web.config enabling the GraphQL UI. If you already have been playing around with JSS you also have an SSC API key in place in de Core Database. If not, make sure to do so.

Setting up the GraphQL configuration in Sitecore

On the JSS website the configuration is just an example (but not a working one). Below is a working example of the schema we are building. We need to do two things to make t work:

  • Define configuration for the schema provider - Under sitecore/api/GraphQL/defaults/content/schemaProviders add an XML element like <Movies type="Macaw.GraphQL.Examples.MovieSchemaProvider, Macaw.GraphQL.Examples"/>. The element name is of your choice and can be anything. This will be used in the next step.

  • Define configuration for the endpoint - Registering your SchemaProvider is not enough. You need to add configuration for your endpoint. In my example below (which you can use as a basis) you'll see that the endpoint is created under sitecore/api/GraphQL/endpoints. Also here you can create an XML node with a name of your choise. you have to define the schema you're using. This can be accomplished by adding <schema hint="list:AddSchemaProvider"><content ref="/sitecore/api/GraphQL/defaults/content/schemaProviders/Movies" param1="master" /></schema>.



Creating a SchemaProvider

For this example I created a class library. When I started developing the SchemaProvider I wanted to expose data from The Movie Database For this I use the TMDbLib Nuget package. After building my library I copied the dll to the bin folder of my website's webroot.

Source for the SchemaProvider class:


using System;
using System.Collections.Generic;
using System.Web;
using GraphQL.Resolvers;
using GraphQL.Types;
using Sitecore;
using Sitecore.Security;
using Sitecore.Security.Accounts;
using Sitecore.Services.GraphQL.Schemas;
using TMDbLib.Client;
using TMDbLib.Objects.Movies;

namespace Macaw.GraphQL.Examples
{
    class MovieSchemaProvider : SchemaProviderBase
    {
        public override IEnumerable CreateRootQueries()
        {
            yield return new MovieQuery();
        }
        protected class MovieQuery : RootFieldType
        {
            public MovieQuery() : base(name: "movieQuery", description: "Gets some movie data")
            {
                QueryArgument[] queryArgumentArray = new QueryArgument[1];
                int index1 = 0;
                QueryArgument queryArgument1 = new QueryArgument();
                queryArgument1.Name = "movieId";
                queryArgument1.Description = "The movie ID to get";
                queryArgumentArray[index1] = (QueryArgument)queryArgument1;

                this.Arguments = new QueryArguments(queryArgumentArray);
            }
            protected override Movie Resolve(ResolveFieldContext context)
            {
                int movieId = context.GetArgument("movieId", (int)383498);

                TMDbClient client = new TMDbClient("APIKEY");
                Movie movie = client.GetMovieAsync(movieId, MovieMethods.Credits | MovieMethods.Similar).Result;
                return movie;
            }
        }
        protected class MovieGraphType : ObjectGraphType
        {
            public MovieGraphType()
            {
                Name = "TMDbLibMovie";

                Field>("id", resolve: context => context.Source.Id);
                Field>("title", resolve: context => context.Source.Title);
                Field< ListGraphType< KeywordGraphType >>("keywords", resolve: context => context.Source.Keywords.Keywords);
                Field>("posterpath", resolve: context => context.Source.PosterPath);
                Field>("overview", resolve: context => context.Source.Overview);
                Field>("backdropPath", resolve: context => context.Source.BackdropPath);
                Field>("budget", resolve: context => context.Source.Budget);
                Field< ListGraphType < GenreGraphType >> ("genres", resolve: context => context.Source.Genres);
                Field>("releaseDate", resolve: context => context.Source.ReleaseDate);
                Field>("voteAverage", resolve: context => context.Source.VoteAverage);
                Field>("voteCount", resolve: context => context.Source.VoteCount);
            }
        }
        protected class GenreGraphType : ObjectGraphType
        {
            public GenreGraphType()
            {
                Name = "TMDbLibMGenre";
                Field>("id", resolve: context => context.Source.Id);
                Field>("title", resolve: context => context.Source.Name);
            }
        }

        protected class KeywordGraphType : ObjectGraphType
        {
            public KeywordGraphType()
            {
                Name = "TMDbLibMKeyword";
                Field>("id", resolve: context => context.Source.Id);
                Field>("title", resolve: context => context.Source.Name);
            }
        }
    }
}

NOTE: this example is using nested classes because it's a very very small schema. A real schema of any size should split the RootFieldTypes and GraphTypes into separate files.

Adding arguments

By default our Query class won't accept any attributes like the movieId in our example. We accomplish this by adding QueryArguments in the contructor of the Query. These are used in the Resolve method where we can get the arguments like in our example: int movieId = context.GetArgument<int>("movieId", (int)383498);

GraphQL FieldTypes

By default we will retrieve the field values as a string. In many cases we need to use other types like booleans, floats, lookups etc. For our demo I constructed two custom GraphTypes and constructed a ListGraphType from this. The result is that I now can also query the genres of the movie rather than getting an exception when querying the field.

Quering the GraphQL API

Now we have deployed our schema and configuration we now can test it. In the configuration I added the url /sitecore/api/graph/items/movies. Hiting the url will now result in the following error: {"errors":[{"message":"SSC API key is required. Pass with 'sc_apikey' query string or HTTP header."}]}. This is because my example forces you to provide the SCC API key. This can be done by adding ?sc_apikey={YOUR_API_KEY} in the querystring. Adding /ui will show the Sitecore Experience Graph Browser.

From here you can test your queries. In the given example I added a movie query:


{
  movieQuery(movieId: "353081") {
    id,
    title,
    posterpath,
    overview,
    budget,
    genres {
      id,
      title
    }
    releaseDate,
    voteAverage,
    voteCount
  }
}	

After firing the query we now get results:

The query is created with ease. Intelisense will help us construct the query. On the right side we can inspect the schema and find all available fields we can access. Happy coding :)

Comments