GraphQL SPQR (GraphQL Schema Publisher & Query Resolver, pronounced like speaker) is a simple-to-use library for rapid development of GraphQL APIs in Java.
This project is on a temporary hiatus. I hope to resume it shortly. In doing so, I must first assess the current state. I have removed the access to issues for the time being to get a chance to clean them up and prevent people from abusing the issues to ask for new releases. I'll restore the access as soon as I can. Sorry for the inconvenience, and thanks for your patience.
- Intro
- Known issues
- Code-first approach
- Installation
- Hello world
- Spring Boot Starter
- Full example
- Full tutorial
- Asking questions
GraphQL SPQR aims to make it dead simple to add a GraphQL API to any Java project. It works by dynamically generating a GraphQL schema from Java code.
- Requires minimal setup (~3 lines of code)
- Deeply configurable and extensible (not opinionated)
- Allows rapid prototyping and iteration (no boilerplate)
- Easily used in legacy projects with no changes to the existing code base
- Has very few dependencies
Due to a 3 year old bug, Kotlin properties produce incorrect AnnotatedType
s on which most of SPQR is based. The most obvious implication is that @GraphQLNonNull
(and other TYPE_USE
annotations) won't work when used on Kotlin properties.
There's nothing that can be done about this from our side so, for the time being, Kotlin support is a non-goal of this project but we will try to be compatible where possible.
There's a bug in OpenJDK's annotation parser that causes annotations on generic type parameters to be duplicated. You may experience this in a form of a mysterious AnnotationFormatError: Duplicate annotation for class: interface io.leangen.graphql.annotations.GraphQLNonNull
occuring
when using @GraphQLNonNull
both on a type and on its generic parameters e.g. @GraphQLNonNull List<@GraphQLNonNull Item>
. Oracle JDK does not have this bug.
Do note it is only relevant which Java compiles the sources, not which Java runs the code. Also note that IntelliJ IDEA comes bundled with OpenJDK, so building the project in IDEA may lead to this error. You should configure your IDE to use the system Java if it is different.
When developing GraphQL-enabled applications it is common to define the schema first and hook up the business logic later. This is known as the schema-first style. While it has its advantages, in strongly and statically typed languages, like Java, it leads to a lot of duplication.
For example, a schema definition of a simple GraphQL type could like this:
type Link {
id: ID!
url: String!
description: String
}
and, commonly, a corresponding Java type would exist in the system, similar to the following:
public class Link {
private final String id;
private final String url;
private final String description;
//constructors, getters and setters
//...
}
Both of these blocks contain the exact same information. Worse yet, changing one requires an immediate change to the other. This makes refactoring risky and cumbersome. On the other hand, if you’re trying to introduce a GraphQL API into an existing project, writing the schema practically means re-describing the entire existing model. This is both expensive and error-prone, and still suffers from duplication.
Instead, GraphQL SPQR takes the code-first approach, by generating the schema from the existing model. This keeps the schema and the model in sync, easing refactoring. It also works well in projects where GraphQL is introduced on top of an existing codebase.
GraphQL SPQR is deployed to Maven Central.
Maven
<dependency>
<groupId>io.leangen.graphql</groupId>
<artifactId>spqr</artifactId>
<version>0.10.0</version>
</dependency>
Gradle
compile 'io.leangen.graphql:spqr:0.10.0'
The example will use annotations provided by GraphQL SPQR itself, but these are optional and the mapping is completely configurable, enabling existing services to be exposed through GraphQL without modification.
Service class:
class UserService {
@GraphQLQuery(name = "user")
public User getById(@GraphQLArgument(name = "id") Integer id) {
...
}
}
If you want to skip adding @GraphQLArgument
, compile with the -parameters
option or the names will be lost.
Domain class:
public class User {
private String name;
private Integer id;
private Date registrationDate;
@GraphQLQuery(name = "name", description = "A person's name")
public String getName() {
return name;
}
@GraphQLQuery
public Integer getId() {
return id;
}
@GraphQLQuery(name = "regDate", description = "Date of registration")
public Date getRegistrationDate() {
return registrationDate;
}
}
To attach additional fields to the User
GraphQL type, without modifyning the User
class, you simply add a query that has User
as the context. The simplest way is using the @GraphQLContext
annotation:
class UserService {
... //regular queries, as above
// Attach a new field called twitterProfile to the User GraphQL type
@GraphQLQuery
public TwitterProfile twitterProfile(@GraphQLContext User user) {
...
}
}
Exposing the service with graphql-spqr:
UserService userService = new UserService(); //instantiate the service (or inject by Spring or another framework)
GraphQLSchema schema = new GraphQLSchemaGenerator()
.withBasePackages("io.leangen") //not mandatory but strongly recommended to set your "root" packages
.withOperationsFromSingleton(userService) //register the service
.generate(); //done ;)
GraphQL graphQL = new GraphQL.Builder(schema)
.build();
//keep the reference to GraphQL instance and execute queries against it.
//this operation selects a user by ID and requests name, regDate and twitterProfile fields only
ExecutionResult result = graphQL.execute(
"{ user (id: 123) {
name,
regDate,
twitterProfile {
handle
numberOfTweets
}
}}");
We're working on a SPQR-powered Spring Boot starter. The project is still very young, but already functional.
See more complete examples using Spring Boot at https://github.com/leangen/graphql-spqr-samples
Coming soon
- Issues Open an issue to report bugs, request features or ask questions
- StackOverflow Use graphql-spqr tag
- Gitter For questions/discussions you don't care to keep for posterity