Home

Awesome

A tutorial for the GraphQL Gradle plugin (client side)

This Tutorial describes how-to create a GraphQL client application, with the graphql-maven-plugin and the graphql Gradle plugin.

The GraphQL plugin helps both on the server and on the client side. You'll find the tutorials for the server side on the Maven server tutorial and on the Gradle server tutorial

A note about non-spring applications

This samples uses Spring to wire the GraphQL Repositories in the application.

GraphQL Repositories also work with non-spring app. You'll find the needed info in the client_graphql_repository wiki's page.

Schema first

This plugin allows a schema first approach.

This approach is the best approach for APIs: it allows to precisely control the Interface Contract. This contract is the heart of all connected systems.

This tutorial won't describe how to create a GraphQL schema. There are plenty of resources on the net for that, starting with the official GraphQL site.

The Forum GraphQL schema

This sample is based on the Forum schema, available here

This schema contains:

This schema is stored in the /src/main/resources/ project folder for convenience.

It could be also be used in another folder, like /src/main/graphql/ . In this case, the schema is not stored in the packaged jar (which is Ok for the Client mode), and you have to use the plugin schemaFileFolder parameter, to indicate where to find this schema.

The pom.xml and build.gradle files

As a Maven or a Gradle plugin, you have to add the plugin in the build:

Let's first have a look at the Maven pom.xml file:


	<properties>
		<graphql-maven-plugin.version>2.4</graphql-maven-plugin.version>
	</properties>
	
	<build>
...
			<plugin>
				<groupId>com.graphql-java-generator</groupId>
				<artifactId>graphql-maven-plugin</artifactId>
				<version>${graphql-maven-plugin.version}</version>
				<executions>
					<execution>
						<goals>
							<goal>generateClientCode</goal>
						</goals>
					</execution>
				</executions>
				<configuration>
					<packageName>org.forum.client</packageName>
					<customScalars>
						<customScalar>
							<graphQLTypeName>Date</graphQLTypeName>
							<javaType>java.util.Date</javaType>
							<graphQLScalarTypeStaticField>com.graphql_java_generator.customscalars.GraphQLScalarTypeDate.Date</graphQLScalarTypeStaticField>
						</customScalar>
					</customScalars>
					<!-- The parameters below change the 1.x default behavior to respect the future 2.x behavior -->
					<copyRuntimeSources>false</copyRuntimeSources>
					<generateDeprecatedRequestResponse>false</generateDeprecatedRequestResponse>
					<separateUtilityClasses>true</separateUtilityClasses>
					<skipGenerationIfSchemaHasNotChanged>true</skipGenerationIfSchemaHasNotChanged>
				</configuration>
			</plugin>
...
		</plugins>
	</build>

	<dependencies>
		<!-- Dependencies for GraphQL -->
		<dependency>
			<groupId>com.graphql-java-generator</groupId>
			<artifactId>graphql-java-runtime</artifactId>
			<version>${graphql-maven-plugin.version}</version>
		</dependency>
	</dependencies>

Define once the plugin version in the build.properties file:

graphQLPluginVersion = 2.4

Then use this version in the build.gradle file:

plugins {
	id "com.graphql_java_generator.graphql-gradle-plugin" version "${graphQLPluginVersion}"
	id 'java'
	id "org.springframework.boot" version "2.4.4"
}

repositories {
	mavenLocal()
	mavenCentral()
}

dependencies {
	// THE VERSION MUST BE THE SAME AS THE PLUGIN's ONE
	implementation "com.graphql-java-generator:graphql-java-client-runtime:${graphQLPluginVersion}"
}

// The line below adds the generated sources as a java source folder in the IDE
sourceSets.main.java.srcDirs += '/build/generated/sources/graphqlGradlePlugin'
sourceSets.main.resources.srcDirs += '/build/generated/resources/graphqlGradlePlugin'


// Let's configure the GraphQL Gradle Plugin:
// All available parameters are described here: 
// https://graphql-maven-plugin-project.graphql-java-generator.com/graphql-maven-plugin/generateClientCode-mojo.html
generateClientCodeConf {
	packageName = 'org.forum.client'
	customScalars = [ [
			graphQLTypeName: "Date",
			javaType: "java.util.Date",
			graphQLScalarTypeStaticField: "com.graphql_java_generator.customscalars.GraphQLScalarTypeDate.Date"
	] ]

	// The parameters below change the 1.x default behavior. They are set to respect the default behavior of the future 2.x versions
	copyRuntimeSources = false
	generateDeprecatedRequestResponse = false
	separateUtilityClasses = true
}

The java version must be set to version 1.8 (or higher).

In this plugin declaration:

The generated source is added to the IDE sources, thanks to:

The graphql-java-runtime dependency add all necessary dependencies, for the generated code. Of course, its version must be the same as the plugin's version.

A look at the generated code

Don't forget to execute (or re-execute) a full build when you change the plugin configuration, to renegerate the proper code:

If you're using eclipse (or other IDE), it may happen (especially with the Gradle plugin, that is less integrated), that you'll have to "Refresh the Gradle plugin" or the "Maven/Update Project...", to make your IDE properly see the generated code.

This will generate the client code in the packageName package (or in the com.generated.graphql if this parameter is not defined)

The code is generated in the :

Let's take a look at the generated code:

To sum up, you'll use:

Choice 1: Partial or Full requests

These are concepts proper to the plugin. You'll find more info about Full and Partial request on this plugins's doc page.

So let's explain that:

Choice 2: Direct or Prepared requests

Use of GraphQL Repositories

The plugin helps to define GraphQL requests with GraphQL Repositories, that are very alike Spring Repositories.

GraphQL Repositories allows you to define GraphQL requests in a Java interface, without writing any Java code. All the necessary code is executed behind the scene. You'll find all the details in the client_graphql_repository wiki's page.

They allow:

You'll find the most representative samples in this tutorial.

Enabling GraphQL Repositories

For use of GraphQL Repositories in non Spring app, please read the client_graphql_repository wiki's page.

When you create GraphQL Repositories in Spring app, you must declare to the Spring container, where to find the GraphQL Repositories you declared.

This is done through the use of the @EnableGraphQLRepositories annotation, on a Spring configuration class. For instance, in a Spring Boot application, it can be added to the main class, like this:

@SpringBootApplication(scanBasePackageClasses = { Main.class, GraphqlClientUtils.class, QueryExecutor.class })
@EnableGraphQLRepositories({ "org.graphql_forum_sample.client" })
public class Main {

	public static void main(String[] args) {
		SpringApplication.run(Main.class, args);
	}
}

In this sample, the GraphQL Repositories are searched in the org.graphql_forum_sample.client package. All sub-packages are searched. You can provide a list of more than one packages, if necessary.

Simple Partial Query/Mutation

GraphQL queries and mutations are executed in exactly the same way.

The easiest way is to execute Partial Request. And the most effective is that this request is prepared first.

The code below executes the boards query, as defined in this extract of the GraphQL schema:

type Query {
    boards: [Board]
[...]
}

Let's define a GraphQL Repository in a Java interface, like this:

@GraphQLRepository
public interface MyGraphQLRepository {

	...   You can defined as many method as you wish in a GraphQL Repository

	/**
	 * Execution of the boards query, with this GraphQL query: {id name publiclyAvailable topics {id title date
	 * nbPosts}}
	 * 
	 * @return The GraphQL server's response, mapped into the POJO generated from the GraphQL schema
	 * @throws GraphQLRequestExecutionException
	 *             Whenever an exception occurs during request execution
	 */
	@PartialRequest(request = "{id name publiclyAvailable topics {id title date nbPosts}}")
	public List<Board> boards() throws GraphQLRequestExecutionException;
}

Then, in your code, just declare the use of the GraphQL Repository in order to use it:

@Component // This annotation marks this class as a Spring bean (prerequisite to make @Autowire annotation work)
public class Application implements CommandLineRunner {

	/** The logger for this class */
	static protected Logger logger = LoggerFactory.getLogger(Application.class);

	@Autowired
	private MyGraphQLRepository myGraphQLRepository;

	@Override
	public void myMethod() throws GraphQLRequestExecutionException {
		logger.info("Boards read: {}", boards);
		
		...
		then do something smart with this result
		...
	}
}

And you're done:

Execution of Mutation works in exactly the same way. Execution of Subscription is slightly different, and again, you'll find the needed information in the client_graphql_repository wiki's page.

Execution of a Query/Mutation with parameters

The query/mutation parameters are the parameters that are defined on the field of the query or mutation, in the GraphQL schema.

Let's execute the topics query, that is defined like this:

type Query {
[...]
    topics(boardName: String!): [Topic]!
[...]
}

The GraphQL Repository contains this additional method:

@GraphQLRepository
public interface MyGraphQLRepository {

	...   You can defined as many method as you wish in a GraphQL Repository

	/**
	 * Execution of the topics query, which has one parameter defined in the GraphQL schema: boardName, that is a
	 * String. If this query had more then one parameter, all its parameters should be parameters of this method, in the
	 * same order a defined in the GraphQL schema, and with the relevant Java type.
	 * 
	 * @param aBoardName
	 * @return
	 * @throws GraphQLRequestExecutionException
	 */
	@PartialRequest(request = "{id date author {id name} nbPosts title content}")
	public List<Topic> topics(String aBoardName) throws GraphQLRequestExecutionException;
}

This query is used this way:

@Component // This annotation marks this class as a Spring bean (prerequisite to make @Autowire annotation work)
public class Application implements CommandLineRunner {

	/** The logger for this class */
	static protected Logger logger = LoggerFactory.getLogger(Application.class);

	@Autowired
	private MyGraphQLRepository myGraphQLRepository;

	@Override
	public void myMethod() throws GraphQLRequestExecutionException {
		List<Topic> topics = myGraphQLRepository.topics("Board name 2");
		
		...
		then do something smart with this result
		...
	}
}

The only change is that all the query parameters are parameter of the queryExecutor.topics(..) method. The topics(..) method take care of serializing and sending the board name parameter to the GraphQL server. Then it reads the response, and maps it into the List<Topic> response type.

Execution of a Query/Mutation with bind parameters

The posts field of the Topic object accepts three parameters:

type Topic {
    id: ID!
    date: Date!
    author: Member!
    publiclyAvailable: Boolean
    nbPosts: Int
    title: String!
    content: String
    posts(memberId: ID, memberName: String, since: Date!): [Post]!
}

So we can update the previous sample, by querying the topic's posts. We'll define these bind parameters:

Of course, mandatory parameter must be given at execution time, whereas optional parameter my remain null.

The GraphQL repository is defined this way:

@GraphQLRepository
public interface MyGraphQLRepository {

	...   You can defined as many method as you wish in a GraphQL Repository

	/**
	 * 
	 * @param boardName
	 *            The parameter of the topics query, as defined in the GraphQL schema
	 * @param memberId
	 *            The bind parameter 'memberId', as defined in this query. It's an optional parameter, as this parameter
	 *            is marked by a '?' character in this query. That is: the provided value may be null, at execution
	 *            time.
	 * @param memberName
	 *            The bind parameter 'memberName', as defined in this query. It's an optional parameter, as this
	 *            parameter is marked by a '?' character in this query. That is: the provided value may be null, at
	 *            execution time.
	 * @param since
	 *            The bind parameter 'since', as defined in this query. It's a mandatory parameter, as this parameter is
	 *            marked by a '&' character in this query. That is: the provided value may NOT be null, at execution
	 *            time.
	 * @throws GraphQLRequestExecutionException
	 *             Whenever an exception occurs during request execution
	 * @return
	 */
	@PartialRequest(requestName = "topics", // The requestName defines the GraphQL defined query. It is mandatory here,
											// as the method has a different name than the query's name in the GraphQL schema
			request = "{id date author {id name} nbPosts title content posts(memberId: ?memberId, memberName: ?memberName, since: &since)  {id date title}}")
	public List<Topic> topicsSince(String boardName, 
			@BindParameter(name = "memberId") String memberId,
			@BindParameter(name = "memberName") String memberName, 
			@BindParameter(name = "since") Date since) throws GraphQLRequestExecutionException;
}

Here is the code that calls the GraphQL repository:

@Component // This annotation marks this class as a Spring bean (prerequisite to make @Autowire annotation work)
public class Application implements CommandLineRunner {

	/** The logger for this class */
	static protected Logger logger = LoggerFactory.getLogger(Application.class);

	@Autowired
	private MyGraphQLRepository myGraphQLRepository;

	@Override
	public void run(String... args) throws GraphQLRequestExecutionException {
		String memberId = null; // may be null, as it's optional
		String memberName = null; // may be null, as it's optional
		Date since = new Calendar.Builder().setDate(2022, 02 - 1 /* february */, 01).build().getTime();
		
		// Let's call the GraphQL server
		List<Topic> topicsSince = myGraphQLRepository.topicsSince("Board name 2", memberId, memberName, since);
		
		... and do something with the Query's result (topicsSince)
	}
}

As there is no provided value for the memberName bind parameter, this parameter is not sent to the server. It's correct as this parameter is optional in both the bind parameter definition in the query (it starts by a ? ) and the GraphQL schema.

Please note that:

Full requests

The previous samples are all Partial requests. This allows to directly get the result of the query in the response of the GraphQL repository method.

The GraphQL Maven and Gradle plugin also manage Full requests (only for query and mutation, not for subscription).

A full request allows to:

The main difference between Partial and Full requests, is that the method that executes a full request returns an instance of the Query or Mutation type, as defined the query or mutation is defined in the GraphQL schema. This means:

Here is a sample of GraphQL Repository method that defines a Full Request:

@GraphQLRepository
public interface MyGraphQLRepository {

	...   You can defined as many method as you wish in a GraphQL Repository

	/**
	 * A full Request returns the Query or the Mutation type, as it is defined in the GraphQL schema. You'll have then
	 * to use the relevant getter(s) to retrieve the request's result
	 * 
	 * @return
	 */
	@FullRequest(request = "fragment topicFields on Topic {id title date} "
			+ "query{boards{id name publiclyAvailable topics {...topicFields nbPosts}}}")
	public Query fullQueryWithFragment() throws GraphQLRequestExecutionException;
}

Then, the using Spring component can call this method like this:

@Component // This annotation marks this class as a Spring bean (prerequisite to make @Autowire annotation work)
public class Application implements CommandLineRunner {

	@Autowired
	private MyGraphQLRepository myGraphQLRepository;

	@Override
	public void run(String... args) throws GraphQLRequestExecutionException {
		Query query = myGraphQLRepository.fullQueryWithFragment(); // Execution of the GraphQL Request
		List<Board> boards = query.getBoards(); // Retrieval of the result

		.. Do something 
	}

}

In this sample, there is a global fragment.

Fragments

You can use fragments in your queries, mutations or subscriptions:

Below is a sample of a GraphQL Repository with Fragments:

@GraphQLRepository
public interface MyGraphQLRepository {

	/**
	 * A full Request returns the Query or the Mutation type, as it is defined in the GraphQL schema. You'll have then
	 * to use the relevant getter(s) to retrieve the request's result
	 * 
	 * @return
	 */
	@FullRequest(request = "fragment topicFields on Topic {id title date} "
			+ "query{boards{id name publiclyAvailable topics {...topicFields nbPosts}}}")
	public Query fullQueryWithFragment() throws GraphQLRequestExecutionException;

	/**
	 * A query with inline fragments
	 * 
	 * @return
	 * @throws GraphQLRequestExecutionException
	 */
	@PartialRequest(requestName = "boards", request = "{id name publiclyAvailable topics {... on Topic {id title date} nbPosts}}")
	List<Board> boardsWithInlineFragment() throws GraphQLRequestExecutionException;
}

Mutations

Mutations work the same way as Queries.

The only difference is the requestType parameter. Its value is query by default. So, for mutation, the requestType parameter is mandatory, in both the @PartialRequest and @FullRequest annotations.

Here is a sample with the mutation, done with a Partial Request, then a Full Request:

@GraphQLRepository
public interface MyGraphQLRepository {

	/**
	 * A mutation sample, within a Partial Request
	 * 
	 * @param postInput
	 *            The post to be created
	 * 
	 * @return The created Post
	 */
	@PartialRequest(request = "{ id date author{id name} title content}", //
			requestType = RequestType.mutation /* default request type for Partial Query is query */)
	Post createPost(PostInput postInput) throws GraphQLRequestExecutionException;

	/**
	 * A mutation sample, within a Full Request
	 * 
	 * @param postInput
	 *            The post to be created
	 * @return The {@link Mutation} type. Calling the {@link Mutation#getCreatePost()} allows to retrieve the return for
	 *         the createPost mutation, that is: the created post
	 */
	@FullRequest(request = "mutation {createPost(post: &postInput) { id date author{id name} title content}}", //
			requestType = RequestType.mutation /* default request type for Partial Query is query */)
	Mutation createPostFullRequest(@BindParameter(name = "postInput") PostInput postInput)
			throws GraphQLRequestExecutionException;
}

And the way to use it is:

@Component // This annotation marks this class as a Spring bean (prerequisite to make @Autowire annotation work)
public class Application implements CommandLineRunner {

	/** The logger for this class */
	static protected Logger logger = LoggerFactory.getLogger(Application.class);

	@Autowired
	private MyGraphQLRepository myGraphQLRepository;

	@Override
	public void run(String... args) throws GraphQLRequestExecutionException {

		TopicPostInput topicInput = new TopicPostInput.Builder().withAuthorId("00000000-0000-0000-0000-000000000001")
				.withPubliclyAvailable(true).withDate(new GregorianCalendar(2019, 4 - 1, 30).getTime())
				.withTitle("a title").withContent("Some content").build();
		PostInput postInput = new PostInput.Builder().withFrom(new GregorianCalendar(2018, 3 - 1, 2).getTime())
				.withInput(topicInput).withTopicId("00000000-0000-0000-0000-000000000002").build();

		logger.info("===========================================================================================");
		logger.info("==================== Executing mutation in a Partial Request ==============================");
		logger.info("===========================================================================================");
		Post postFromPartialRequest = myGraphQLRepository.createPost(postInput);

... Do something with the created post (postFromPartialRequest)


		logger.info("===========================================================================================");
		logger.info("==================== Executing mutation in a Full Request =================================");
		logger.info("===========================================================================================");
		Mutation mutation = myGraphQLRepository.createPostFullRequest(postInput);
		Post postFromFullRequest = mutation.getCreatePost();

... Do something with the created post (postFromFullRequest)

	}

}

Subscriptions

Subscription are documented on the GraphQL plugin's web site