-
Notifications
You must be signed in to change notification settings - Fork 183
3. Advanced Configuration
GraphQL SPQR can work with both Jackson and Gson. SPQR first checks for Jackson on the classpath and prefers that if it's available. If not, it checks for Gson, and uses that. So additional config is only needed if both libraries are available but Gson is preferred:
//Updated from Getting Started
GraphQLSchema schema = new GraphQLSchemaGenerator()
.withBasePackages("io.leangen")
.withOperationsFromSingleton(userService)
.withValueMapperFactory(new GsonValueMapperFactory())
.generate();
GraphQL SPQR support a vast list of methods to influence the way the GraphQL Schema is generated. This allow you to dynamically alter any GraphQL Types that are generated by GraphQL SPQR.
In some cases the GraphQL type generated from you Domain class may need additional fields that aren't available in the Domain class. There are 2 ways you can add fields to an existing GraphQL type without having to modify the Domain class.
If you wish to extend your GraphQL Type with an additional field the easiest way to do this is to use the @GraphQLContext annotation.
Let's say we want to extend the User type from the Getting Started with a new field "twitterProfile", which returns a TwitterProfile type. First we need the TwitterProfile type.
class TwitterProfile {
@GraphQLQuery
public String getHandle() {
...
}
@GraphQLQuery
public int getNumberOfTweets() {
...
}
}
Now we have to let SPQR know that this TwitterProfile is a part of the User type. We can do this using the @GraphQLContext annotation. We update the UserService with the following method:
class UserService {
//existing queries
public TwitterProfile getTwitterProfile(@GraphQLContext User user) {
...
}
}
And that's all there is to it. Now you can query the TwitterProfile like so:
ExecutionResult result = graphQL.execute(
"{ user (id: 123) {
name,
regDate,
twitterProfile {
handle
numberOfTweets
}
}}");
If the @GraphQLContext method is not sufficient for your use case, for example if the additional fields are configured rather than coded, you can use a TypeMapper. SPQR uses TypeMappers to transform Java classes to GraphQL Types. By creating your own TypeMapper you can influence the resulting GraphQL Type.
The easiest way to achieve this is by extending SPQR's default ObjectTypeMapper and override the getFields() and supports() methods.
public class CustomTypeMapper extends ObjectTypeMapper {
@Override
protected List<GraphQLFieldDefinition> getFields(String typeName, AnnotatedType javaType, TypeMappingEnvironment env) {
//Retrieve the fields from the annotated methods by calling the super#getFields() method.
List<GraphQLFieldDefinition> fields = super.getFields(typeName, javaType, env);
//Now add a custom field
fields.add(GraphQLFieldDefinition.newFieldDefinition()
.name("twitterHandle")
.type(GraphQLString)
.build());
// Append a custom DataFetcher (also known as a Resolver)
DataFetcher<?> dataFetcher = e -> {
User user = (User) e.getSource();
return getTwitterHandleForUser(user);
};
// And register the DataFetcher to the code registry
env.buildContext.codeRegistry.dataFetcher(coordinates(typeName, "twitterHandle"), dataFetcher);
return fields;
}
@Override
public boolean supports(AnnotatedElement element, AnnotatedType type) {
return ClassUtils.getRawType(type).equals(User.class);
}
}
There are a couple of things happening in the code above:
The supports method checks what kind of class is provided and will return true if the proved AnnotatedType is a User class. This makes sure that the getFields() method is only called for the User class.
When the getFIelds() method is called it will initially call the ObjectTypeMapper's getFields() method to retrieve all fields based on the Java type, as is SPQR's default behavior. Afterward a new GraphQLFieldDefinition is created to represent the twitterHandle field, and it's added to the list of fields generated by the ObjectTypeMapper.
Although the field is now attached to the User object, GraphQL does not know where to get the data for that field from. To resolve this we need to provide the GraphQL Schema with a DataFetcher, or Resolver as they are sometimes called, to retrieve the data for the twitterHandle field.
The DataFetcher then needs to be registered to the code registry, which keeps track of all DataFetchers and TypeResolver within the Schema. When registering the DataFetcher you will need to provide the coordinates to which this DataFetcher applies, meaning the name of the type (User) in combination with the name of the field (twitterProfile).
When you've created your TypeMapper you need to let SPQR know that this TypeMapper should be used. Fortunately that is very easy and can be done like this:
GraphQLSchema schema = new GraphQLSchemaGenerator()
.withBasePackages("io.leangen") //not mandatory but strongly recommended to set your "root" packages
.withOperationsFromSingleton(userService) //register the service
.withTypeMappers(new CustomTypeMapper())
.generate(); //done ;)
For more information on DataFetchers please refer to the GraphQL Java documentation.