Rene Rubalcava | June 16, 2019

GraphQL has grown in popularity over the past couple of years. Where GraphQL shines is in its descriptive nature of querying data.

If you want to write a query for the Star Wars API to get all the film titles, it might look something like this.

{ allFilms{ edges{ node{ title } } } }

The query is JSON-like, but it's not JSON. You can learn more about GraphQL on the tutorials page.

Apollo provides a client API you can use to work with GraphQL. It saves you some work of writing your own POST requests, so I highly recommend you learn it. Apollo provides libraries to integrate with Angular and React, but so far not one for Dojo. But that's ok, because you can use the Apollo Client to build your own GraphQL integration.

Looking at react-apollo, they have an ApolloProvider that you can use to wrap components of your application. This provides the Apollo client to components. Those components can then use a Query higher order component to pass the query and client and thus display the result.

How hard can that be?

Trust the Process

When working with Dojo, most of your work with external APIs is probably going to be done in a process. We covered this topic in detail in this post.

Here is what a generic process for working with GraphQL might look like.

import { createProcess , createCommandFactory } from "@dojo/framework/stores/process" ; import { add } from "@dojo/framework/stores/state/operations" ; const commandFactory = createCommandFactory < { client : any ; data : any ; loaded : boolean ; } > ( ) ; const fetchData = commandFactory ( async ( { path , payload } ) => { const { client , query } = payload ; const { data } = await client . query ( { query } ) ; return [ add ( path ( "data" ) , data ) , add ( path ( "loaded" ) , true ) ] ; } ) ; export const fetchDataProcess = createProcess ( "fetch-data" , [ fetchData ] ) ;

This process will take a given apollo client instance and a GraphQl query to fetch some results. This works pretty well because it's not tied to any particular endpoint or data structure, even though it is currently typed as any for client and data. I could try to work around that with some generics, but wanted to keep this example fairly simple.

Put it in a box

We can tie this together with a widget and Dojo container.

import { tsx } from "@dojo/framework/widget-core/tsx" ; import { WidgetBase } from "@dojo/framework/widget-core/WidgetBase" ; import { Store } from "@dojo/framework/stores/Store" ; import { StoreContainer } from "@dojo/framework/stores/StoreInjector" ; import { fetchDataProcess } from "../processes/apolloProcess" ; import ApolloClient from "apollo-boost" ; interface QueryProps { client ? : ApolloClient < any > ; query: string; data?: any; loaded?: boolean; fetchData?: (args: any) => void; } // base widget that handles displaying children that use the Query export class BaseQuery extends WidgetBase<QueryProps, any> { onAttach ( ) { const { client , query , loaded , fetchData } = this . properties ; if ( ! loaded && query ) { fetchData ( { client , query } ) ; } } protected render ( ) { const { loaded , data } = this . properties ; return this . children . map ( child => { if ( typeof child === "function" ) { return child ( { loading : ! loaded , data } ) ; } return child ; } ) ; } } function getProperties(store: Store<{ data: any; loaded: boolean }>): any { const { get , path } = store ; return { data : get ( path ( "data" ) ) , loaded : get ( path ( "loaded" ) ) , fetchData : fetchDataProcess ( store ) } ; } // Use a StoreContainer export const Query = StoreContainer(BaseQuery, "state", { getProperties } );

In this snippet we provide a BaseQuery that is going to handle taking any queries that child widgets might provide and use those queries to to fetch some data. This widget uses a StoreContainer to pass the store that is updated using our process to the BaseQuery . We can call this container a Query to keep it simple. This is going to allow us to write some code like the following.

export class MyWidget extends WidgetBase < { client : any } > { protected render ( ) { const { client } = this . properties ; return ( < Query query = { query } client = { client } > { ( { loading , data } ) => { if ( loading ) { return < span > Loading... </ span > ; } else { return < div classes = { [ css . root ] } > { parseMyData ( data ) } </ div > ; } } } </ Query > ) ; } }

Be a good provider

At this point you might be asking yourself, How do I pass a client to a widget that uses this?

Good question. Technically, you could create the client in your Widget module and provide it to <Query> . But that seems kind of icky to bind backend concerns into my UI code. The way react-apollo does this is by providing an <ApolloProvider> that you can give a client and then wrap your application components with it. These components will have access to the Apollo client to give to the Query higher order component.

It basically looks like the <ApolloProvider> provides its client property to child widgets. I can do that.

import { tsx } from "@dojo/framework/widget-core/tsx" ; import { WidgetBase } from "@dojo/framework/widget-core/WidgetBase" ; export class ApolloProvider extends WidgetBase < { client : any } > { protected render ( ) { const { client } = this . properties ; for ( let child of this . children ) { if ( ( child as any ) . properties ) { ( child as any ) . properties . client = client ; } } return this . children ; } }

What this naive ApolloProvider does is iterates over the children of the widget and injects the client property into each one. I'm sure the react-apollo implementation does much more, but I'm not going to argue with what works.

Now that I have my provider, I can start to tie it all together.

The great provider

In my main.tsx where I initialize my Dojo application, I can create my ApolloClient and pass it my ApolloProvider that will wrap my other widgets so that I can use it.

... import { Store } from "@dojo/framework/stores/Store" ; import { registerStoreInjector } from "@dojo/framework/stores/StoreInjector" ; import ApolloClient from "apollo-boost" ; import { ApolloProvider } from "./providers/ApolloProvider" ; import { Countries } from "./widgets/Countries" ; const client = new ApolloClient ( { uri : "https://countries.trevorblades.com" } ) ; const store = new Store ( ) ; const registry = registerStoreInjector ( store ) ; class App extends WidgetBase { protected render ( ) { return ( < div > < ApolloProvider client = { client } > < h2 > { "\u2728 dojo-apollo \u2728" } </ h2 > < Countries /> </ ApolloProvider > </ div > ) ; } } ...

The sample GraphQL API I am going to use provides a list of Countries. So I'm going to write a widget that can display those results.

GraphQL results

Here is where we get to see the fruit of our labors! We can write a widget that will display a specific set of data from our GraphQL API. So the widget can provide its own GraphQL query. This makes sense when you think of the widget as owning this query.

import { tsx } from "@dojo/framework/widget-core/tsx" ; import { WidgetBase } from "@dojo/framework/widget-core/WidgetBase" ; import gql from "graphql-tag" ; import * as css from "./styles/Countries.m.css" ; import { Query } from "../containers/QueryContainer" ; interface Country { name : string ; code : number ; } export const query = gql ` { countries { name code } } ` ; const countryItems = ( countries : Country [ ] = [ ] ) => countries . map ( ( { name , code } ) => ( < li classes = { [ css . item ] } key = { code } > < a classes = { [ css . link ] } key = { code } href = { ` https://en.wikipedia.org/wiki/ ${ name } ` } target = " _blank " > { name } </ a > </ li > ) ) ; export class Countries extends WidgetBase < { client ? : any } > { protected render ( ) { const { client } = this . properties ; return ( < Query query = { query } client = { client } > { ( { loading , data } ) => { if ( loading ) { return < span > Loading... </ span > ; } else { return < ul classes = { [ css . root ] } > { countryItems ( data . countries ) } </ ul > ; } } } </ Query > ) ; } }

This widget uses our Query container to wrap up the part of the widget that relies on the GraphQL results. This looks pretty much exactly like react-apollo .

You can see this entire example in action in this code sandbox.

Summary

This is a fairly simple implementation of a GraphQL <Query> and <ApolloProvider> , but it works pretty well in a case like this. If you have multiple different queries you want to run in a single application, I think you would need to create a factory method for your containers to define multiple states that would contain different results.

This is definitely something I want to continue working on in the future and I think there might be some more Dojo way of handling this in some features that look to be coming to Dojo in the future.

As always, have fun with it and keep on hacking!