GraphQL Server and Client

· January 25, 2020

GraphQL is a query language for your API. It is also a specification which determines the validity schema on the server. It is strongly typed, the schema defines the GraphQL API’s type system. It defines the possible objects that a client can access. A client can find information about the schema via introspection.

In this blog post we are going to develop a simple GraphQL server and client in Kotlin/Java. First we define the GraphQL schema with the following root types.

type Query {
    bookById(id: ID!): Book
    books: [Book!]
}

type Mutation {
    addReview(input: AddReviewInput!): Boolean
}

We will be able to retrieve books by their id, retrieve all books and to add a review to a book. All types in GraphQL are nullable by default, with ! we can declare that the field is non nullable.

type Book {
    id: ID!
    title: String!
    pageCount: Int
    publishedDate: Date
    author: Author!
    reviews: [Review]
}

type Author {
    firstName: String!
    lastName: String!
}

type Review {
    stars: Int!
    comment: String!
}

input AddReviewInput {
    bookId: ID!
    stars: Int!
    comment: String!
}

scalar Date

Server

For the GraphQL Server we use the Java implementation https://www.graphql-java.com/ For the bookById, books and addReview fields we define a DataFetcher.

@Configuration
class GraphqlConfiguration(private val graphqlDataFetchers: GraphqlDataFetchers,
                           @Value("classpath:graphql/*.graphqls")
                           private val resources: Array<Resource>) {

    @Bean
    fun graphQL(): GraphQL? {
        return GraphQL.newGraphQL(buildGraphQLSchema()).build()
    }

    private fun buildGraphQLSchema(): GraphQLSchema {
        val schemaParser = SchemaParser()
        val typeDefinitionRegistry = TypeDefinitionRegistry()
        for (resource in resources) {
            typeDefinitionRegistry.merge(schemaParser.parse(resource.file))
        }
        val schemaGenerator = SchemaGenerator()
        return schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, buildRuntimeWiring())
    }

    private fun buildRuntimeWiring(): RuntimeWiring {
        return RuntimeWiring.newRuntimeWiring()
                .scalar(ExtendedScalars.Date)
                .type(TypeRuntimeWiring.newTypeWiring("Query")
                        .dataFetcher("bookById", graphqlDataFetchers.bookByIdDataFetcher)
                        .dataFetcher("books", graphqlDataFetchers.books))
                .type(TypeRuntimeWiring.newTypeWiring("Mutation")
                        .dataFetcher("addReview", graphqlDataFetchers.addReview()))
                .build()
    }
}

We can put a GraphQL API on any persistence layer. In this example we use Spring Data JPA.

@Component
class GraphqlDataFetchers(private val bookRepository: BookRepository,
                          private val reviewRepository: ReviewRepository) {

    val bookByIdDataFetcher: DataFetcher<Book?>
        get() = DataFetcher { env: DataFetchingEnvironment ->
            val id = env.getArgument<String>("id")
            bookRepository.findById(UUID.fromString(id))
        }
    
    ...
}

We define repositories for the following entities: Book, Author and Review.

interface BookRepository : Repository<Book, UUID> {
    fun save(book: Book): Book
    fun findById(id: UUID): Book?
    @EntityGraph(value = "books") fun findAll() : List<Book>
}

interface AuthorRepository : Repository<Author, UUID> {
    fun save(author: Author): Author
}

interface ReviewRepository : Repository<Review, UUID> {
    fun save(review: Review): Review
}

where the entities have the following definitions:

@Entity
@Table(name = "books")
@NamedEntityGraph(name = "books", attributeNodes = [NamedAttributeNode("author"),NamedAttributeNode("reviews")])
class Book(
        var title: String,
        @Column(name = "page_count") var pageCount: Int,
        @Column(name = "published_date") var publishedDate: LocalDate,
        @ManyToOne(fetch = FetchType.LAZY) var author: Author,
        @OneToMany @JoinColumn(name = "book_id") var reviews: MutableSet<Review> = HashSet(),
        @Id @GeneratedValue(generator = "UUID") @Type(type = "uuid-char") var id: UUID? = null) {
    fun addReview(review: Review) {
        reviews.add(review)
    }
}

@Entity
@Table(name = "authors")
class Author(
        @Column(name = "first_name")
        var firstName: String,
        @Column(name = "last_name")
        var lastName: String,
        @Id @GeneratedValue(generator = "UUID") @Type(type = "uuid-char") var id: UUID? = null)

@Entity
@Table(name = "reviews")
class Review(
        var stars: Int,
        var comment: String,
        @Id @GeneratedValue(generator = "UUID") @Type(type = "uuid-char") var id: UUID? = null
)

A simple GraphQL query

query {
  books {
    title  
  }
}

could return a response like

{
  "data": {
    "books": [
      {
        "title": "The Da Vinci Code"
      },
      {
        "title": "Memoirs of a Geisha"
      },
      {
        "title": "The Alchemist"
      }
    ]
  }
}

Behind the scene it will fire the following SQL query to the database

Hibernate: 
    select
        book0_.id as id1_1_,
        book0_.author_id as author_i4_1_,
        book0_.page_count as page_cou2_1_,
        book0_.title as title3_1_ 
    from
        book book0_

The @NamedEntityGraph and @EntityGraph make sure to avoid the N+1 query problem. For the query

query Books {
  books {
    title
    author{
      firstName
    }
    reviews {
      stars
      comment
    }
  }
}     

instead of having many queries on the authors and reviews tables we have only one query:

select
        book0_.id as id1_1_0_,
        author1_.id as id1_0_1_,
        reviews2_.id as id1_2_2_,
        book0_.author_id as author_i5_1_0_,
        book0_.page_count as page_cou2_1_0_,
        book0_.published_date as publishe3_1_0_,
        book0_.title as title4_1_0_,
        author1_.first_name as first_na2_0_1_,
        author1_.last_name as last_nam3_0_1_,
        reviews2_.comment as comment2_2_2_,
        reviews2_.stars as stars3_2_2_,
        reviews2_.book_id as book_id4_2_0__,
        reviews2_.id as id1_2_0__ 
    from
        books book0_ 
    left outer join
        authors author1_ 
            on book0_.author_id=author1_.id 
    left outer join
        reviews reviews2_ 
            on book0_.id=reviews2_.book_id

One more thing to note here that we didn’t define a controller with /graphql endpoint. A GraphQLController is defined by using the following dependency.

dependencies {
    implementation("com.graphql-java:graphql-java-spring-boot-starter-webmvc:1.0")
    ...
}

Client

With curl this is how we make a simple GraphQL request:

$ curl -i -X POST http://localhost:8080/graphql -H "Content-Type:application/json" -d '{"query":"query { books {title} } "}'

In GraphQL the request methods is always POST, wheather it is a query or a mutation.

With Java we can use Spring’s RestTemplate to send a GraphQL POST request.

ResponseEntity<String> response = restTemplate.exchange(graphqlEndpointUrl, HttpMethod.POST, request, String.class);

We can load the GraphQL query request from the classpath. A mutation query would look like this:

mutation AddReview($input: AddReviewInput!) {
    addReview(input:$input)
}

where we externalised the AddReviewInput into a json file.

[
  {
    "input": {
      "bookId": "b41c35c8-3b39-428d-a84b-97aebc7a1602",
      "stars": 5,
      "comment": "Its all about following your dream and about taking the risk of following your dreams"
    }
  },
  {
    "input": {
      "bookId": "c5ec9fb6-b586-449f-b869-bfe4057a4f0e",
      "stars": 5,
      "comment": "It is like eating fancy dessert at a gourmet restaurant"
    }
  }
]

We can build multipe GraphQL queries with the externalised inputs using the variables field

List<String> createJsonQueries(String graphql, String operationName, String variables) throws JsonProcessingException {
    ObjectNode wrapper = objectMapper.createObjectNode();
    wrapper.put("query", graphql);
    wrapper.put("operationName", operationName);
    List<String> queries = new ArrayList<>();
    JsonNode jsonNode = objectMapper.readTree(variables);
    for (int i = 0; i < jsonNode.size(); i++) {
        wrapper.set("variables", jsonNode.get(i));
        queries.add(objectMapper.writeValueAsString(wrapper));
    }
    return queries;
}

GraphQL is introspecitve. With the __schema query we can list all types in the schema

query {
  __schema {
    types {
      name
      kind
      description
      fields {
        name
      }
    }
  }
}

With the __type query we can get details about a type

query {
  __type(name: "Book") {
    name
    kind
    description
    fields {
      name
    }
  }
}

GraphiQL is a great tool to explore a GraphQL API. Normally you include a pre-bundled version into your GraphQL server. Then based on the schema definition which it gets from the GraphQL Server and you can start playing with the API. Here is live demo https://graphql.github.io/swapi-graphql/

GraphQL Playground is another popular GraphQL tool. It can be used even as a desktop app. It has query history, configuration of HTTP headers and tabs. graphql-playground

Conclusion

In this blog post we saw how easy to create a simple GraphQL server and client. The code examples can be found in this repository https://github.com/altfatterz/graphql-demos

Twitter