This Post is a documented setup of a Spring Boot GraphQL-Project. If you are looking for an even shorter way, you may want to check out the sample project on Github: github.com/felbit/spring-boot-graphql-demo.
As a mathematician I really like graphs and as a computer scientist my heart beats for types. So let's talk about typed graphs, shall we?
Table of contents
This post is partly a documentation for my future me to know how to get started with a GraphQL project in Spring Boot again, since I did invest some time to get the basic setup up and running. So the setup is key. The data in here is just sample data without any real world meaning. To have any data, we will sketch a boring old blog example. So expect blog posts and authors.
This post is not an article about Spring Boot, PostgreSQL or JPA. I will start a simple Spring Boot project and show all the code to follow along, but I will not dive deeper into any of the topics.
What is GraphQL?
REST is the go to tool for defining APIs today. It works perfectly as long as you follow a resource oriented programming pattern. That often includes that you know about the whereabouts of the queried data. If a client needs data from multiple resources, REST quickly falls apart and tends to over-deliver on the data end.
"GraphQL is the better REST", is stated on the tutorial page How to GraphQL. GraphQL is not an API, though. It is a query language for calling an API that is defined through typed schemas to be used by GraphQL. GraphQL also provides language independent specification for implementing a server that can handle the graph queries. So a GraphQL implementation is the server runtime that understands GraphQL queries.
Yada, yada ... show me the code! - Here is some:
Literally the first thing we see is type. GraphQL schemas are typed by the GraphQL type system.
The syntax for fields is always the following:
where the field name may be any name to your liking. It may be a good idea to find a descriptive field name since this is the key in the requesting queries. The type has to be a type in the schema's known type system. That may be primitives like Long or complex types like String or types you have defined yourself, like Foo in the example above. After all the exclamation mark defines that a return value is not null. It kind of is the opposite of Haskell's Maybe type (or Java's Optional).
Spring Boot Setup
The easiest way to start a fresh Spring Boot project is by utilising the Initializr. Choose a Maven Project, Java and whatever version of Spring Boot is currently the stable (by the time of this writing it's 2.1.4). Give your project a group and an artifact identifier and add the following dependencies: Web, JPA and PostgreSQL. If you are familiar with Gradle and / or Kotlin, you may also use these. The proceeding concepts should not differ.
You click generate project, choose a location, unzip and open the project with the editor or IDE of your choice. If you try to run the project as it is you will get a database error. That does make sense since we haven't configured any database connection, yet. Let's to that!
Let's add a database to your Postgres installation. If you have none on your system, here is a good source to get you started.
Within your project's src/main/resources you find your application.properties file. In there add the following to connect to your Postgres database:
Hit that "run" button again and you should be rewarded with something like the following:
Started GraphqlDemoApplication in 4.839 seconds
Yeah, we have a Spring Boot application with JPA and PostgreSQL up and running! Ok, enough of the small talk - let's get that GraphQL up.
If you have ever worked with a Spring Boot application before, adding GraphQL to your project is straight forward. Just add the com.graphql-java dependencies graphql-java-tools and graphql-spring-boot-starter to your Maven (or Gradle) configuration.
To do that, open the pom.xml file in your project's root directory and add the following lines to the dependencies section:
Your version numbers may vary. Reimport your project dependencies and restart the server. Nothing should break at this point (since we actually haven't written any code yet). So, let's break things.
Adding a GraphQL Schema
The schema is essentially the API definition. It defines, which queries your backend understands. This is written in singular since there is only one graph. We will split the graph in parts and store the parts in multiple files but there must be one root to the graph and one root only.
We start by adding a schema.graphqls file to the src/main/resources directory.
To get a simple blog running, some sort of article could be relevant. So let's add an article definition:
This adds a type Article to our schema definition. Like String, Long or ID, Article now becomes part of the type system of our GraphQL schema. Reminder: The exclamation mark (!) after the type declaration indicates that the return value is not null.
Ok, fun. But how do I get the articles from my backend?
You just defined your first query. Congratulations! By the way: there is only one query. All further query definitions will extend this query. That does make sense in the context of a Type. Query is just a type like String or Long or Article.
But wait! Why is there an exclamation mark after the result? What happens, if there is no article, yet? Right: you'll get an empty list back. Hence, you'll always get a list, never null.
Fun, again. But what if I just need one article? Do you know the id? Then let's define a query to get an article by its id:
No exclamation mark at the end here - since the requested article may not be there. But you must provide an id here.
Another nice feature would be, to add articles to your blog, wouldn't it? Here you go:
Oh, a new Type: Mutation. As a Query type is intended to deliver information about your current state of the world, the Mutation type is intended to - well - mutate the state. So you now know enough, to finalise your article CRUD:
Ok, fun and games. But where does the data come from?
Adding a Model
To map our API graph to a database we have to provide a data model and a resolver class for each query type. Adding a model is straight forward Spring Boot. In the src/main/java/<yourpackagename> package create a model package and therein an entity class Article:
Add constructors and getters and setters or - my preferred way - use the project lombok library to handle that for you. I will not cover lombok in this tutorial, you can head over to Baeldung and get a proper introduction there.
Next we need a repository for our model data. That is easy thanks to Spring Boot. In the repository package create an Interface like this:
Next we need a resolver for the GraphQL query. Create a package named resolver and add the Java class Query there:
Now we need a second resolver for our Mutation type. That's analogue to the Query resolver. In the resolver package add a class called Mutation with the following content:
At this point we have a fully working GraphQL backend. All queries of the Type Query and Mutation are fully functional and we can operate on the Article type.
Since we don't have any frontend client at that point, it is not that easy to verify, that our queries actually work as intended. GraphiQL comes to the rescue. It is similar to the famous swagger in that sense, that it lets us explore our API in a browser.
GraphiQL is added through your pom.xml file as follows:
Reload your dependencies, restart your application and navigate to http://localhost:8080/graphiql and try your API:
Fancy, huh? What would be even more fancy? Having an author to our article, that would even be more fancy. So, let's add one - and with that, a bit of complexity.
Adding a Complex Field
Adding the Author type is straight forward:
And with that some queries and mutations:
It's important, that the additions are added to the existing type definitions. There is only one Query type and only one Mutation type.
By the way: There are more built-in types than just Query and Mutation. One example would be the Subscription type for reactive resources. But those are out of the scope of this tutorial. We will come back to that in another post in the future.
You have the API definition for the Author type. One thing is missing, though. We would like to connect Author and Articles. For that we add the Author type as a field to the Article type (and we don't want the author to be null):
The API definition is complete.
Adding Author Model, Repository, Query and Mutation
Adding the author model in the model package is straight forward:
And so is the author repository in the repository package:
The Query class contains no surprises:
Nor does the Mutation class:
If we had not connected the types Article and Author we would be done by now. But two steps are missing at this point to satisfy the connection: we have to add the Author to the Article class and we have to write a specific resolver for the connection between Article and Author.
Connecting Article and Author
Adding the author to the article class is nothing new and exciting:
For complex fields - like author here - getter and setter are not enough, though. We have to explicitly resolve the connection. Java-GraphQL provides an interface named GraphQLResolver to do exactly that. Inside the resolver package we add a new class ArticleResolver:
Restart your server and everything should work fine. Et voilà! Your GraphQL Project is ready to be extended!
Bonus: Splitting the Graph
I like to keep my graph nice and tidy. Therefore I tend to organise it into modules referencing the types. In src/main/resources add a directory named graphql. In there add two files, one for each type: Article.graphqls and Author.graphqls:
That leaves us with a nice and tidy schema.graphqls:
You don't even need that. If you define the Author type as your graph root by deleting the "extend" keywords before Query and Mutation, you can delete the schema.graphqls entirely. But I prefer this setup.
You find all the code in a public repository at github.com/felbit/spring-boot-graphql-demo.