Skip to content

client_faq

etienne-sf edited this page Jun 22, 2024 · 35 revisions

Configuring the GraphQL client

Use the Spring capabilities with a non-spring App

To use advanced features (like Spring Security, OAuth2, https personalization...) you'll need to execute the app with a Spring Context.

But you can keep your non-spring app with this context: you'll then have to start a spring app that will load the Spring Context, and the GraphQL components. Then you call your main() entry point, and your application starts. It can then retrieve the GraphQL query/mutation/subscription executors to execute GraphQL requests.

You'll find the working samples in the Forum sample:

The NonSpringWithSpringGraphQLConfMain is the new Main class, with its main() method. It's a Spring app, and it load the Spring context to wire everything that is needed for the GraphQL requests to be executed. You'll see at the end of the class the static getters that allows to retrieve the QueryExecutor and MutationExecutor executors.

This NonSpringWithSpringGraphQLConfMain.run(String... args) calls your existing (or to be) OldMain.main(args) method, to start your code, once everything is ready.

Doing this allows you to use all the GraphQL wiring, including using the generated code. And your own code can remaing a 'standard' java code, without Spring injection.

@SpringBootApplication(scanBasePackageClasses = { MinimalSpringApp.class, GraphQLConfiguration.class,
		QueryExecutor.class })
public class NonSpringWithSpringGraphQLConfMain implements CommandLineRunner {

	/**
	 * This singleton allows the static getters to retrieve the Spring components that have been autowired from Spring
	 */
	private static NonSpringWithSpringGraphQLConfMain nonSpringWithSpringGraphQLConfApp;

	/**
	 * The executor, that allows to execute GraphQL queries. The class name is the one defined in the GraphQL schema.
	 */
	@Autowired
	QueryExecutor queryExecutor;

	/**
	 * The executor, that allows to execute GraphQL mutations. The class name is the one defined in the GraphQL schema.
	 * It will be null if no mutation has been defined.
	 */
	@Autowired(required = false)
	MutationExecutor mutationExecutor;

	/**
	 * The executor, that allows to execute GraphQL subscriptions. The class name is the one defined in the GraphQL
	 * schema. It will be null if no subscription has been defined.
	 */
	@Autowired(required = false)
	SubscriptionExecutor subscriptionExecutor;

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

	/**
	 * This method is started by Spring, once the Spring context has been loaded. This is run, as this class implements
	 * {@link CommandLineRunner}
	 */
	@Override
	public void run(String... args) throws Exception {
		nonSpringWithSpringGraphQLConfApp = this;
		// The Spring context is now created, including the GraphQL stuff. Let's start the non Spring app
		OldMain.main(args);
	}

	/**
	 * Getter for the {@link QueryExecutor} that has been loaded by Spring
	 * 
	 * @return
	 */
	public static QueryExecutor getQueryExecutor() {
		return nonSpringWithSpringGraphQLConfApp.queryExecutor;
	}

	/**
	 * Getter for the {@link SubscriptionExecutor} that has been loaded by Spring
	 * 
	 * @return
	 */
	public static SubscriptionExecutor getSubscriptionExecutor() {
		return nonSpringWithSpringGraphQLConfApp.subscriptionExecutor;
	}
}

The old Main class uses the static getters of the NonSpringWithSpringGraphQLConfMain, to retrieve the query, mutation and subscription executors:

public class OldMain {

	public static void main(String[] args) throws Exception {
		// A basic demo of input parameters
		@SuppressWarnings("deprecation")
		Date date = new Date(2019 - 1900, 12 - 1, 20);

		// For this simple sample, we execute a direct query. But prepared queries are recommended.
		// Please note that input parameters are mandatory for list or input types.
		System.out.println(
				"Executing query: '{id name publiclyAvailable topics(since: &param){id}}', with input parameter param of value '"
						+ date + "'");

		// In the below line, NonSpringWithSpringGraphQLConfApp static getter is used to retrieve the QueryExecutor
		System.out.println(NonSpringWithSpringGraphQLConfMain.getQueryExecutor()
				.boards("{id name publiclyAvailable topics(since: &param){id}}", "param", date));

		System.out.println("Normal end of the application");
	}
}

Define the GraphQL server URL

The way to define the GraphQL server endpoint varies, depending on the kind of app you uses:

For Spring app, you'll update the application.properties or application.yml file, for instance:

graphql.endpoint.url = http://localhost:8180/my/updated/graphql/path

For non Spring app, you'll define the GraphQL server endpoint with one of these ways:

  • Call the static com.graphql_java_generator.client.request.AbstractGraphQLRequest.setStaticConfiguration(GraphQLConfiguration) method, directly, or through GraphQLRequest that you've got from the query, mutation or subscription class (see the Execute GraphQL Requests page for more info on getting a GraphQLRequest.
    • As this method is static, this configuration is then available for all your GraphQLRequests. This is nice if you attack only one GraphQL server.
  • Call the setInstanceConfiguration(GraphQLConfiguration) method of your GraphQLRequest that you've got from the query, mutation or subscription class (see the Execute GraphQL Requests page for more info on getting a GraphQLRequest.
    • This allows to define various configuration, depending on your GraphQLRequests, including calling several GraphQL servers.
  • Create your query, mutation or subscription executor instance, from the generated code. For instance, if your GraphQL schema defines a Query, you can create an instance of it like this: QueryExecutor executor = new QueryExecutor("https://your.server.com/your/graphql/path");

Connect to more than one GraphQL servers

Starting with the 1.17 release, when in client mode, you can execute requests against more than one GraphQL servers.

You'll find all the information On this page

Connect to an OAuth2 protected server

You'll find all the info on the OAuth2 client page.

Get the data part of the response when an error occurs

The generate code throws an exception when the server returns GraphQL errors in its response. As this can happen despite the fact the the request has been treated (totally or partially), the plugin tries to parse the _data _ part of the response. The parsed data is attached to the thrown GraphQLRequestExecutionException or GraphQLRequestExecutionUncheckedException, along with the full response. They can be retrieved with the getData() and getResponse() methods.

Add new Exchange Filter Function (includes add specific headers to the request)

Base tutorial (baeldung)

A Spring Exchange Filter Function allows to interact with the request or with the response. You'll find more information in the Spring apidoc, and a sample in the Baeldung site.

To do this, you'll need to define your own Spring Exchange Filter Function, than to override the Spring webClient provided by the plugin.

Sample for this plugin, to manage Shopify client token

A sample for this is available in this sample made by Latsode: Shopify Client Spring.

Steps to reproduce

Step 1: define your own Spring Exchange Filter Function

To do this, you can take a look at the com.graphql_java_generator.client.OAuthTokenExtractor class, that builds an exchange filter that adds OAuth headers. It's located in the graphql-java-client-runtime module.

There a lots of other samples on the net, depending on your own use case.

Step 2: override the Spring webClient provided by the plugin

To do this, you must override the webClient bean.

To do this, you'll just have to mark your own Spring bean with @Primary. This makes your bean override the bean provided by the default configuration.

Starting with 1.18 version, it's possible to attack several GraphQL servers AND each server has its own generated Spring autoconfiguration class. The Spring beans each GraphQL server is identified thanks to the bean suffix. So if you defined a bean suffix in your pom.xml or gradle.build file (mandatory for several GraphQL server), do not forget to add it in all beans and parameters name for this server,

Then you can define your own webClient bean, that can accept other Exchange Filter Function, and mark it with @Primary for instance:

@Primary
@Bean
	public WebClient webClient(String graphqlEndpoint, //
			@Autowired(required = false) @Qualifier("httpClient") HttpClient httpClient,
			@Autowired(required = false) @Qualifier("serverOAuth2AuthorizedClientExchangeFilterFunction") 			ServerOAuth2AuthorizedClientExchangeFilterFunction serverOAuth2AuthorizedClientExchangeFilterFunction,
			...
			your own exchange filter functions or other specific parameters
			...) {
		return GraphQLConfiguration.getWebClient(graphqlEndpoint, httpClient,
				serverOAuth2AuthorizedClientExchangeFilterFunction,
				... your own exchange filter functions
				);
	}

Change the generated code (custom templates)

The generated code is created from Velocity templates. You can override any of these templates, according to your needs.

You'll find the needed info on the Custom Templates page.

Questions about GraphQL request execution

What's the difference between GraphQL variables and bind parameter?

GraphQL Variables are defined in the GraphQL spec:

  • They are a GraphQL standard
  • They respect strict syntactic rules
  • They can be used only in Full Request, as they must be declared before used (with the exact GraphQL type)
  • They can map a full field argument. For instance: "mutation creation($human:HumanInput!){createHuman(human:$human){...}}
  • They can map just part of a field argument. For instance: "mutation creation($name:String!){createHuman(human:{name:$name, ...}){...}}
  • Technically :
    • The GraphQL request is sent as is (with the '$human' or '$name' in it, in the previous samples).
    • The Graphql variable value is sent in the json variables field
    • The variable content is resolved on server side

Bind parameters are proper to this plugin:

  • They are not a GraphQL standard
  • They are easier to use (you don't have to declare them first)
  • They can be used in both Full Requests and Partial Requests, as you don't have to declare them first
  • They can be mandatory (if starting with '&') or optional (if starting with '?')
    • If no value is provided at request execution time for a mandatory bind parameter, a GraphQLRequestExecutionException exception is thrown
    • If no value is provided at request execution time for an optional bind parameter, then this parameter is not sent to the GraphQL server
  • They must map a full field argument.
    • For instance, this full query is valid "mutation {createHuman(human:&human){...}}
    • For instance, this full query is not valid "mutation {createHuman(human:{name:&name, ...}){...}}
  • Technically :
    • The bind parameters are replaced on client side by their value, before the GraphQL request is sent to the server
  • A bind parameter value may not contain GraphQL variable in its field values.

HowTo retrieve the extensions GraphQL response field?

This field is an optional field, described in the GraphQL spec. It contains a Map, and the values for this map is free, and may be anything, as choosed by the GraphQL server implementation.

To retrieve its value, you can do, for instance:

@Component
class AClass {
	
	@Autowired
	MyQueryExecutor myQuery;
		
	public void doSomething() {
		// Retrieve the result as a full query
		MyQuery resp = myQuery.exec("{directiveOnQuery}"); 
		
		// You can then retrieve the whole extensions field as a map
		Map<String, JsonNode> map = resp.getExtensionsAsMap();
		
		// Or retrieve just a value, from a key. This uses Jackson to deserialize 
		// the jsonNode into the target class for this key
		YouClass value = resp.getExtensionsField("YourKey", YourClass.class);
		
		... Do something useful
	}
}

How to execute a query/mutation which returns a scalar?

For Full requests, the request is the same as in graphiql:

@Component
public class YourClass {

	@Autowired
	QueryExecutor queryExecutor;

	GraphQLRequest request;
	
	@PostConstruct
	public void init() {
		//From the Forum sample: nbBoards is a query that returns an Int, and has no arguments
		request = queryExecutor.getTopicsGraphQLRequest("query {nbBoards}");
	}
	
	int nbBoards() {
		return request.execQuery().getNbBoards();
	}
}

For Partial Requests, just give an empty response field list:

@Component
public class YourClass {

	@Autowired
	QueryExecutor queryExecutor;

	GraphQLRequest request;
	
	@PostConstruct
	public void init() {
		//From the Forum sample: nbBoards is a query that returns an Int, and has no arguments
		request = queryExecutor.getNbBoardsGraphQLRequest("");
	}
	
	int nbBoards() {
		return queryExecutor.nbBoards(request);
	}
}

How to execute a request with all parameters set in the String request?

If you provide a full string, that contains all the parameters, you can do this:

public class MyClass {

	@Autowired
	MutationExecutor mutationExecutor;
	
	public void myMethod() {
		GraphQLRequest graphQLRequest = new GraphQLRequest(//
				"mutation {createHuman (human:  {name: \\\"a name with a string that contains a \\\\\\\", two { { and a } \\\", friends: [], appearsIn: [JEDI,NEWHOPE]} )"
						+ "@testDirective(value:?value, anotherValue:?anotherValue, "
						+ "anArray  : [  \\\"a string that contains [ [ and ] that should be ignored\\\" ,  \\\"another string\\\" ] , \r\n"
						+ "anObject:{    name: \\\"a name\\\" , appearsIn:[],friends : [{name:\\\"subname\\\",appearsIn:[],type:\\\"\\\"}],type:\\\"type\\\"})   "//
						+ "{id name appearsIn friends {id name}}}"//
		);

		// You can can execute the full query, without providing any parameter (as everything is set in the provided request
		Human human = mutationExecutor.execWithBindValues(graphQLRequest, null).getCreateHuman();
	}
}

How to use the graphql-transport-ws for all requests (query, mutation and subscription)

In its standard behavior, the generated code uses the graphql-transport-ws only for subscriptions.

To use it also for queries and mutations, you have to override the default RequestExecution component.

With Spring, you'll have to override the default one, by adding a bean declaration in your spring configuration class, like below:

(do not forget to add the bean suffix in all beans and parameters name, if you defined one in your pom.xml or gradle.build file)

@Configuration
public class MySpringConfigurationClass {

		@Bean
		@Primary // Allows 'your' bean to override the default one
		public RequestExecution requestExecution(String graphqlEndpoint, //
				@Autowired(required = false) @Qualifier("graphqlSubscriptionEndpoint") String graphqlSubscriptionEndpoint, //
				@Autowired(required = false) @Qualifier("webClient") WebClient webClient, //
				@Autowired(required = false) @Qualifier("webSocketClient") WebSocketClient webSocketClient,
				@Autowired(required = false) @Qualifier("serverOAuth2AuthorizedClientExchangeFilterFunction") ServerOAuth2AuthorizedClientExchangeFilterFunction serverOAuth2AuthorizedClientExchangeFilterFunction,
				@Autowired(required = false) @Qualifier("oAuthTokenExtractor") OAuthTokenExtractor oAuthTokenExtractor) {
			// Returns the Request executor that can execute queries, mutations and subscriptions according to the
			// graphql-transport-ws protocol
			return new RequestExecutionGraphQLTransportWSImpl(graphqlEndpoint,
					graphqlSubscriptionEndpoint, webClient,
					webSocketClient, serverOAuth2AuthorizedClientExchangeFilterFunction,
					oAuthTokenExtractor);
		}

...

}

You'll find a working sample in this test.

General questions

What does the plugin generate?

When configuring the graphql-maven-plugin in client mode, it reads a GraphQL schema file, and generates all the necessary code to make it easy to call a GraphQL server.

As an overview, it generates:

  • One Executor class for each Query/Mutation/Subscription object. These Executors contain all the methods that allow to execute a full query, and shortcut methods to execute the queries, mutations and subscriptions.
    • The introspection queries (__schema and __type) are added to the query defined in the GraphQL schema. For "memory", you must provide a query in every GraphQL schema.
  • One POJO for each standard object of the GraphQL object:
    • One java interface for each GraphQL union and interface
    • One java class for each GraphQL type and input type, including the query, mutation and subscription (if any). If the GraphQL type implements an interface, then its java class implements this same interface
    • One java enum for each GraphQL enum
  • One GraphQLRequest object, that allows to store and use your prepared queries
  • All the necessary runtime is actually attached as source code into your project: the generated code is stand-alone.
    • So, your project, when it runs, doesn't depend on any external dependency from graphql-java-generator.
    • This is why we call it an accelerator: you can generate the code once, and get rid of graphql-java-generator if you wish. BTW: we think its better to continue using it! This allows you to benefit from the enhancements the come next. But you're free to leave, and keep the generated code. :)
  • You can change this default behavior, and use the runtime into an external dependency

There is a compilation error after a plugin upgrade

Here are the main reasons (and solutions) that could be the source of this trouble:

  • If you changed the plugin version, you have to force the full regeneration of the generated code. So don't forget to do a maven clean install or a gradlew clean build.
  • Perhaps you changed the plugin's version, but not the plugin runtime's version? Double check that their version are
  • If you used custom templates, check if there was changes in the original custom templates. We may great efforts to let them as stable as possible. But if they changed, you may have to report theses changes into your custom templates.

Use graphqls in a jar dependency

To do this, you currently need to unzip the jar (typically in the target or buid folder), and use the schemaFileFolder parameter to indicate to the plugin where the GraphQL schema are. More info on the plugin doc page.

To unzip the jar, you can use the plugin Apache Maven Dependency Plugin, in its dependency:unpack

Clone this wiki locally