Show Navigation

Building a GORM/GraphQL App with React and Apollo

Build a Grails app and use GORM's GraphQL support to serve React app using Apollo

Authors: Zachary Klein

Grails Version: 4.0.0

1 Training

Grails Training - Developed and delivered by the folks who created and actively maintain the Grails framework!.

2 Getting Started

In this guide you will learn how to leverage GORM’s support for GraphQL. You will configure Grails to act as a GraphQL server,

The initial sample project already has a simple conference-related REST API (consisting of two domain classes, Speaker and Talk) configured using the @Resource annotation. The project is using the react profile, and a simple React app has been provided which will list out the speakers, as well as the talks belonging to each speaker.

The React app is quite trivial and naively uses the default REST API provided by Grails - e.g, the app makes one REST call to list the speakers, and then iterates over the talk ids for each speaker, making a separate REST call to get the details for each talk. This means that a single load of the app can require many REST calls simply to acquire the data needed to render the app.

http://localhost:8080/speaker	200	fetch	1.6 KB	851 ms
http://localhost:8080/talk/1	200	fetch	260 B	262 ms
http://localhost:8080/talk/2	200	fetch	273 B	310 ms
http://localhost:8080/talk/3	200	fetch	254 B	299 ms
http://localhost:8080/talk/4	200	fetch	261 B	297 ms
http://localhost:8080/talk/5	200	fetch	293 B	284 ms

Of course there are a few ways we could resolve this problem using standard REST techniques, including hypermedia, or customized endpoints (using JSON Views, for example) - however in more complex apps it is possible that this could lead to a proliferation of custom endpoints providing different combinations of data. In addition, some endpoints might provide more data than the app actually requires - there is no way to express the desired "piece" of data in a typical REST API.

This is where GraphQL shines - it allows consumers of our API, whether private clients (like our own Single-Page Applications) or public users, to specify the exact pieces of data that they need, in a declarative way. GraphQL queries can even be codifed as Flow types, making the data requirements of your components explicit and testable.

GraphQL

Please refer to the excellent documentation and other resources at the official GraphQL project website for more information.

2.1 What you will need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 8 or greater installed with JAVA_HOME configured appropriately

2.2 How to complete the guide

To get started do the following:

or

The Grails guides repositories contain two folders:

  • initial Initial project. Often a simple Grails app with some additional code to give you a head-start.

  • complete A completed example. It is the result of working through the steps presented by the guide and applying those changes to the initial folder.

To complete the guide, go to the initial folder

  • cd into grails-guides/gorm-graphql-with-react-and-apollo/initial

and follow the instructions in the next sections.

You can go right to the completed example if you cd into grails-guides/gorm-graphql-with-react-and-apollo/complete

3 Configuring GORM for GraphQL

Install the GORM GraphQL plugin by adding this line to the dependencies section in build.gradle:

build.gradle
compile "org.grails:gorm-graphql:1.0.2"
compile "org.grails.plugins:gorm-graphql:1.0.2"

Now, to generate a GraphQL schema for your domain classes, all that is needed is to add a static graphql property to the domain class in question.

grails-app/domain/com/objectcomputing/Talk.groovy
package com.objectcomputing

import grails.rest.Resource

@Resource(uri='/talk')
class Talk {

    String title
    int duration

    static graphql = true (1)

    static belongsTo = [speaker: Speaker]
}
1 This will cause a GraphQL schema to be generated based on the properties in the domain class, following the constraints specified in the constraints block.

If you need to customize the behavior of the GraphQL schema, you can assign the graphql property to a custom GraphQLMapping, using the DSL provided by the plugin. This is not a requirement for this guide, however a few of the supported options are shown in the Speaker domain class.

grails-app/domain/com/objectcomputing/Speaker.groovy
package com.objectcomputing

import grails.rest.Resource
import org.grails.gorm.graphql.entity.dsl.GraphQLMapping
import java.time.LocalDate
import java.time.Period

@Resource(uri='/speaker')
class Speaker {

    String firstName
    String lastName
    String name
    String email
    String bio
    LocalDate birthday

    static hasMany = [talks: Talk]

    static graphql = GraphQLMapping.build {

        property 'lastName', order: 1 (1)
        property 'firstName', order: 2
        property 'email', order: 3

        exclude 'birthday' (2)

        property 'name', deprecationReason: 'To be removed August 1st, 2020' (3)

        property('bio') { (4)
            order 4
            dataFetcher { Speaker speaker ->
                speaker.bio ?: "No biography provided"
            }
        }

        add('age', Integer) { (5)
            dataFetcher { Speaker speaker ->
                Period.between(speaker.birthday, LocalDate.now()).years
            }
            input false
        }
    }

    static constraints = {
        email nullable: true, email: true
        birthday nullable: true
        bio nullable: true
    }

    static mapping = {
        bio type: 'text'
        name formula: 'concat(FIRST_NAME,\' \',LAST_NAME)'
        talks sort: 'id'
    }

}
1 Set property ordering
2 Exclude property from the schema
3 Deprecate a property (can also be set to deprecated: true)
4 Custom data fetcher, for providing custom logic when retrieving the property
5 Adding a (non persistent) property to the GraphQL schema
You can learn all the available features of the GORM GraphQL plugin from the plugin documentation.

4 Playing with the GraphQL browser

The GORM GraphQL plugin provides an interactive GraphQL browser (GraphiQL) which makes it easy to explore the GraphQL schema. The browser is available when the Grails app is running, at the URL /graphql/browser. The browser features include autocompletion based on your schema, making it easy to explore the available types of queries and mutations (GraphQL terminology for requests that create/update/delete data from the schema).

Start up the app with either grails run-app or ./gradlew bootRun, and browse to http://localhost:8080/graphql/browser.

You can now run queries against your new GraphQL schema, as well as mutations. Here’s a few queries and mutations to get your started - refer to the GORM GraphQL plugin documentation and the GraphQL project website to learn more about the syntax and features of GraphQL.

Queries

//List speaker ids
query {
  speakerList(max: 10) {
    id
  }
}
//Return total speakers
query {
	speakerCount
}
//Retrieve speaker details by id
query {
  speaker(id: 1) {
    firstName
    lastName
    bio
  }
}
//Return list of talks with speaker details embedded
query {
	talkList(max:10) {
    title
    speaker {
      firstName
      lastName
    }
  }
}

Mutations

//Update speaker by id, return any error messages (if any)
mutation {
  speakerUpdate(id: 1, speaker: {
    bio: "Updated bio!"
  }) {
    id
    bio
    errors {
      field
      message
    }
  }
}'
//Create speaker - this mutation will return an error due to missing property
mutation {
  speakerCreate(speaker: {
    firstName: "James"
    lastName: "Kleeh"
  }) {
    id
    firstName
    lastName
    errors {
      field
      message
    }
  }
}
//Delete speaker by id
mutation {
  speakerDelete(id: 2) {
    error
  }
}

5 Install and Configure Apollo

Now that you have a GraphQL schema, it is time to replace the existing RESTful integration in the React app using GraphQL. Of course we could compose GraphQL queries manually, however in most cases you will be better served by using a library to abstract away the details of making these requests. One option is the Relay framework, which is developed by Facebook (the creators of React). Another popular library is Apollo, which offers bindings for numerous JavaScript frameworks, including Angular and React. This guide will be using Apollo in the following steps.

Install the necessary packages using either npm or yarn:

~ npm install apollo-client-preset react-apollo graphql-tag graphql --save

The first requirement to use Apollo is to configure a provider, which will encapsulate the details needed to actual make requests to the GraphQL schema. Edit the file src/main/webapp/index.js as shown below:

client/src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './css/bootstrap.css';
import './css/App.css';
import './css/grails.css';
import './css/main.css';
import {ApolloProvider} from 'react-apollo';
import {ApolloClient} from 'apollo-client';
import {createHttpLink} from 'apollo-link-http';
import {InMemoryCache} from 'apollo-cache-inmemory';
import { SERVER_URL} from "./config";

(1)
const client = new ApolloClient({
  link: createHttpLink({ uri: `${SERVER_URL}/graphql` }), (2)
  cache: new InMemoryCache()
});

(3)
ReactDOM.render(< ApolloProvider client={client}>
  <App />
</ApolloProvider>, document.getElementById('root'));
1 We create an instance of ApolloClient, which requires two objects - an HttpLink (which represents the URL of the GraphQL schema) and a cache store.
2 This client will send queries to the ${SERVER_URL}/graphql endpoint - which conveniently happens to be where the GORM GraphQL plugin exposes the GraphQL schema
3 Finally, we wrap our App component with an ApolloProvider component, passing our ApolloClient instance as a prop to the latter. Now all child components will be able to query/mutate the GraphQL schema pointed to by the client.

6 Querying GraphQL

Our React app is fairly simple, consisting of 3 main components, SpeakerList, Speaker, and Talk. The first two of these components include REST calls to load their data. Let’s replace these calls with a GraphQL query.

Edit src/main/webapp/SpeakerList.js

/src/main/webapp/SpeakerList.js
import React, {Component} from 'react'
import Speaker from "./Speaker";
import {graphql} from 'react-apollo'; (1)
import gql from 'graphql-tag';  (2)

class SpeakerList extends Component {

(3)
  render() {
    const speakers = this.props.data.speakerList; (4)
    return speakers ? [speakers.map(s => <Speaker key={s.id} speaker={s}/>)] : <p>Loading...</p>
  }
}

const SPEAKER_QUERY = gql`query {        speakerList(max: 10) { (5)
  id, firstName, lastName,
    talks { id }
  }
}`;

(6)
const SpeakerListWithData = graphql(SPEAKER_QUERY,
    {options: {pollInterval: 5000}} (7)
)(SpeakerList);
export default SpeakerListWithData; (8)
1 The graphql is a higher-order component, which means that it wraps an existing component and returns a new component with additional behavior.
2 gql is JavaScript template literal tag that will parse our GraphQL queries
3 Note that we were able to completely remove our componentDidMount method (where the REST call had been made) and our constructor, as we no longer need to use this.state.
4 The data returned by our GraphQL query will be available via this.props.data.[name of query] - in this case, speakerList.
5 This constant represents our query - note that the syntax is the same as what you might have used in the GraphiQL browser.
6 Rather than exporting SpeakerList directly, we first "wrap" it using the graphql higher-order component, which will "connect" our SpeakerList component with our GraphQL schema, pass in our data requirements, and provide the data via the data prop within SpeakerList.
7 We’ve specified a pollInterval, which is one of several options available from graphql. This option will cause our GraphQL query to be run once every 10 seconds, updating our component with any new data automatically.
8 Finally, we export the "connected" version of SpeakerList as the default module.

At this point, if you restart the application (or run ./gradlew webpack in another terminal, to reload the JavaScript), you will see the React app is now retrieving the speakers via the GraphQL schema.

Moving on the the Speaker component, we can again remove the componentDidMount method, and we can remove the this.state.talks property. Instead, in the render method, we will access each speakers' talks directly from the speaker prop.

/src/main/webapp/Speaker.js
import React, {Component} from 'react'
import {Well} from 'react-bootstrap'
import Talk from "./Talks";

class Speaker extends Component {

  constructor() {
    super();

    this.state = {
      title: '',
      duration: ''
    }
  }

//... other methods ommitted for space

  render() {
    const {speaker} = this.props;
    const {title, duration} = this.state;

    return <Well>
      <h3>{speaker.firstName} {speaker.lastName}</h3>
      <hr/>

      <ul>
        {speaker.talks.map(t => <Talk key={t.id} talk={t} delete={() => this.deleteTalk(t.id)}/>)}
        <li>
          <label>Title</label>
          <input type='text' name='title' value={title}
                 onChange={this.handleNewTalkChange}/>

          <label>Duration</label>
          <input type="number" name="duration" value={duration}
                 onChange={this.handleNewTalkChange}/>
          <button onClick={this.addNewTalk}>Add Talk</button>
        </li>
      </ul>

    </Well>
  }
}

export default Speaker;

<1> <2>

However, if we try to run the app now we will find that we only have access to the ids of each talk - we are missing title and duration. Thanks to GraphQL, we can easily add these properties to our retrieved data by simply adding them to the query.

Edit src/main/webapp/SpeakerList.js and add title and duration to the SPEAKER_QUERY.

/src/main/webapp/SpeakerList.js
const SPEAKER_QUERY = gql`query {
speakerList(max: 10) {
  id, firstName, lastName,
    talks { id, title, duration } (1)
  }
}`;

With that, our multiple REST calls have been replaced with a single GraphQL query. You can inspect this query, and the response, using your Apollo’s developer tools browser extension.

Apollo Dev Tools Extension

7 Mutations

As mentioned earlier, mutations are GraphQL’s mechanism for changing the data via the GraphQL schema. The available mutations are defined in the schema. The GORM GraphQL plugin generates several basic mutations, described in the documentation. For example, for our Speaker domain class, the plugin provides the following mutations:

  • speakerCreate(book: {})

  • speaker(id: .., book: {})

  • speaker(id: ..)

Of course, custom mutations can be defined as well - the plugin documentation describes how.

Our React app already uses REST calls to create and delete talks for a given speaker. Let’s replace these REST calls with GraphQL mutations.

Edit /client/src/Speaker.js:

/client/src/Speaker.js
const talkCreate = gql`
    mutation talkCreate($talk: TalkCreate!) {
        talkCreate(talk: $talk) {
            id
            title
            duration
        }
    }
`;

const talkDelete = gql`
    mutation talkDelete($id: Long!) {
        talkDelete(id: $id) {
            error
        }
    }
`;

We’ve defined GraphQL fragments for two mutations, talkCreate and talkDelete. These fragments specify the inputs required by each mutation, and their types. You can type these mutations into the GraphiQL browser and see their required inputs.

Now we need to bind these mutations to our component props, so we can call them from our component. Again, we use the higher-order graphql component for this task. Because we have multiple mutations, we will import Apollo’s compose function to streamline the "chaining" of these mutations.

First, import the compose function at the top of Speaker.js:

import {graphql, compose} from 'react-apollo'
import gql from 'graphql-tag'

Now we can use compose to bind these mutations to our Speaker component.

client/src/Speaker.js
const SpeakerWithMutations = compose(
  graphql(talkCreate, {
      props: ({mutate}) => ({
          talkCreate: ({talk}) =>
            mutate({
                variables: {talk},
            })
      })
  }),
  graphql(talkDelete, {
      props: ({mutate}) => ({
          talkDelete: ({id}) =>
            mutate({
                variables: {id},
            })
      })
  })
)(Speaker)

export default SpeakerWithMutations;
For more information regarding the use of mutations and compose, please refer to the Apollo documentation.

The last step is to call our mutations in place of REST calls. Find the addNewTalk and deleteTalk functions in Speaker.js, and edit them as shown below:

client/src/Speaker.js
addNewTalk = () => {
    const {title, duration} = this.state;
    const {speaker} = this.props;

    this.setState({title: '', duration: ''});

    this.props.talkCreate({talk: {title, duration, speaker: {id: speaker.id}}})
      .then(({data}) => console.log('create response:', data))
      .catch((error) => console.log('there was an error sending the query', error));
};

deleteTalk = (id) => {
    this.props.talkDelete({id: id})
      .then(({data}) => console.log('delete response: ', data))
      .catch((error) => console.log('there was an error sending the query', error));
};

Each of the mutations we’ve bound to our component are available as functions off of this.props: talkCreate() and talkDelete(). Each function takes a single argument, which is a JavaScript object containing all the parameters required by our mutation. When the mutation resolves, the then() function will be called with the data that is returned by the mutation. For this sample, we are simply logging out the data, but you could also use this to update the Apollo cache as described in the documentation.

For simplicity, this guide is relying on the pollingInterval we set up earlier to refresh the data and remove/add talks from the cache. In a real-world application, you will want to update the data in memory using the result of the mutation.

8 Conclusion

In this guide we’ve seen how easy is to create a full-featured GraphQL schema with Grails, and to interact with that schema in a Single-Page-App, using Apollo and the React Profile for Grails. Please refer to the GORM for GraphQL Documentation, and related Grails & React Guides to learn more.

9 Help with Grails

Object Computing, Inc. (OCI) sponsored the creation of this Guide. A variety of consulting and support services are available.

OCI is Home to Grails

Meet the Team