Spring Boot GraphQL Starter

tl;dr: 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 matematician I really like graphs and as a computer scientist my heart beats for types. So let’s talk about typed graphs, shall we?

Preface

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, comments and authors.

This post is not an article about Spring Boot, Postgres 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 one of the topics.

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 multiples resources, REST quickly falls apart and tends to over delivering on the data end.

“GraphQL is the better REST”, as it’s 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:

### This is a simple example for a schema definition
# not a sample for the following implementation
# if you just scrolled up to find the schema,
# please scroll down again.

type Foo {
    id: ID!
    title: String
    bar: Bar
}

type Bar { 
    id: ID!
    name: String
}

type Query {
    findAllFoos: [Foo]!
    countFoos: Long!
    countBars: Long!
}

type Mutation {
    newFoo: Foo!
    deleteFoo: Boolean!
    newBar: Foo!
    ...
}

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:

<fieldName>: <Type>[!]

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

The easiest way to start a fresh Spring Boot project is by utilising the Initializr. We 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.

Screenshot 2019-04-10 at 08.06.37.png

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!

Adding Postgres

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.

postgres=# CREATE ROLE sbgraphql WITH PASSWORD 'sbgraphql';
postgres=# CREATE DATABASE sbgraphql_demo OWNER sbgraphql;

Within your project’s src/main/resources you find your application.properties file. In there add the following to connect to your Postgres database:

## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url=jdbc:postgresql://localhost:5432/sbgraphql_demo
spring.datasource.username=sbgraphql
spring.datasource.password=sbgraphql
spring.jpa.hibernate.ddl-auto=update

# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect

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.

Adding GraphQL

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:

<!-- ... -->
<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>com.graphql-java</groupId>
        <artifactId>graphql-java-tools</artifactId>
        <version>5.2.4</version>
    </dependency>

    <dependency>
        <groupId>com.graphql-java</groupId>
        <artifactId>graphql-spring-boot-starter</artifactId>
        <version>5.0.2</version>
    </dependency>
</dependencies>
<!-- ... -->

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:

type Article {
    id: ID!
    title: String!
    text: String!
}

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?

type Query {
    findAllArticles: [Article]!
}

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:

type Query {
    article(id: ID!): Article
    findAllArticles: [Article]!
}

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:

type Mutation {
    newArticle(title: String!, text: String!): Article!
}

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:

type Article {
    id: ID!
    title: String!
    text: String!
}

type Query {
    article(id: ID!): Article
    findAllArticles: [Article]!
}

type Mutation {
    newArticle(title: String!, text: String!): Article!
    updateArticle(id: ID!, title: String!, text: String!): Article
    deleteArticle(id: ID): Boolean!
}

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 a entity class Article:

package de.sandstorm.springboot.graphqldemo.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Article {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    private String title;

    private String text;
    
}

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, but head over to Baeldung and have a proper introduction there.

Next we need a repository for our model data. That is easy as pie thanks to Spring Boot. In the repository package create an Interface like this:

package de.sandstorm.springboot.graphqldemo.repository;

import de.sandstorm.springboot.graphqldemo.model.Article;
import org.springframework.data.repository.CrudRepository;

public interface ArticleRepository extends CrudRepository<Article, Long> { }

Done.

Next we need a resolver for the GraphQL query. Create a package named resolver and add the Java class Query there:

package de.sandstorm.springboot.graphqldemo.resolver;

import com.coxautodev.graphql.tools.GraphQLResolver;
import de.sandstorm.springboot.graphqldemo.model.Article;
import de.sandstorm.springboot.graphqldemo.repository.ArticleRepository;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
public class Query implements GraphQLResolver {

    private ArticleRepository articleRepository;

    public Query(ArticleRepository articleRepository) {
        this.articleRepository = articleRepository;
    }

    public Optional<Article> article(Long id) {
        return articleRepository.findById(id);
    }

    public Iterable<Article> findAllArticles() {
        return articleRepository.findAll();
    }

}

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:

package de.sandstorm.springboot.graphqldemo.resolver;

import com.coxautodev.graphql.tools.GraphQLMutationResolver;
import de.sandstorm.springboot.graphqldemo.model.Article;
import de.sandstorm.springboot.graphqldemo.repository.ArticleRepository;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
public class Mutation implements GraphQLMutationResolver {

    private ArticleRepository articleRepository;

    public Mutation(ArticleRepository articleRepository) {
        this.articleRepository = articleRepository;
    }

    public Article newArticle(String title, String text) {
        Article article = new Article();
        article.setTitle(title);
        article.setText(text);

        articleRepository.save(article);

        return article;
    }

    public Optional<Article> updateArticle(Long id, String title, String text) {
        Optional<Article> article = articleRepository.findById(id);

        article.ifPresent(a -> {
            a.setTitle(title);
            a.setText(text);
            articleRepository.save(a);
        });

        return article;
    }

    public boolean deleteArticle(Long id) {
        articleRepository.deleteById(id);
        return true;
    }

}

At this point we have a full working GraphQL backend. All queries of the Type Query and Mutation are fully functional and we can operate on the Article type.

Adding GraphiQL

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 let’s us explore our API in a browser.

GraphiQL is added through your pom.xml file as follows:

<!-- ... -->
<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphiql-spring-boot-starter</artifactId>
    <version>5.0.2</version>
</dependency>
<!-- ... -->

Reload your dependencies, restart your application and navigate to http://localhost:8080/graphiql and try your API:

 

Screenshot 2019-04-11 at 06.49.57.png

Nice, 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 complexity.

Adding a Complex Field

Adding the Author type is straight forward:

type Author {
    id: ID!
    first_name: String
    last_name: String!
}

And with that some queries and mutations:

type Query {
    ...
    author(id: ID!): Author
    findAllAuthors: [Author]!
}
type Mutation {
    newArticle(title: String!, text: String!, authorId: ID!): Article!
    updateArticle(id: ID!, title: String!, text: String!, authorId: ID!): Article
    deleteArticle(id: ID): Boolean!

    newAuthor(firstName: String!, lastName: String!): Author!
    updateAuthor(id: ID!, firstName: String!, lastName: String!): Author
    deleteAuthor(id: ID): Boolean!
}

It’s important, that the additions belong in the already written type definitions. There is only one Query type and only one Mutation type.

By the way: There are more build 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):

type Article {
    id: ID!
    title: String!
    text: String!
    author: Author!
}

The API definition is complete.

Adding Author Model, Repository, Query and Mutation

Adding the author model in the model package is straight forward:

package de.sandstorm.springboot.graphqldemo.model;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@Getter @Setter @NoArgsConstructor
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    private String firstName;

    private String lastName;

    public Author(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

}

And so is the author repository in the repository package:

package de.sandstorm.springboot.graphqldemo.repository;

import de.sandstorm.springboot.graphqldemo.model.Author;
import org.springframework.data.repository.CrudRepository;

public interface AuthorRepository extends CrudRepository<Author, Long> { }

The Query class contains no surprises:

package de.sandstorm.springboot.graphqldemo.resolver;

// import ...
import de.sandstorm.springboot.graphqldemo.repository.AuthorRepository;

@Component
public class Query implements GraphQLQueryResolver {

    private ArticleRepository articleRepository;

    private AuthorRepository authorRepository;

    public Query(ArticleRepository articleRepository, AuthorRepository authorRepository) {
        this.articleRepository = articleRepository;
        this.authorRepository = authorRepository;
    }

    //...

    public Optional<Author> author(Long id) {
        return authorRepository.findById(id);
    }

    public Iterable<Author> findAllAuthors() {
        return authorRepository.findAll();
    }

}

Nor does the Mutation class:

package de.sandstorm.springboot.graphqldemo.resolver;

// import ...
import de.sandstorm.springboot.graphqldemo.model.Author;
import de.sandstorm.springboot.graphqldemo.repository.AuthorRepository;


@Component
public class Mutation implements GraphQLMutationResolver {

    private ArticleRepository articleRepository;
    private AuthorRepository authorRepository;

    public Mutation(ArticleRepository articleRepository, AuthorRepository authorRepository) {
        this.articleRepository = articleRepository;
        this.authorRepository = authorRepository;
    }

    // ...

    public Author newAuthor(String firstName, String lastName) {
        Author author = new Author();
        author.setFirstName(firstName);
        author.setLastName(lastName);

        authorRepository.save(author);

        return author;
    }

    public Optional<Author> updateAuthor(Long id, String firstName, String lastName) {
        Optional<Author> author = authorRepository.findById(id);

        author.ifPresent(a -> {
            a.setFirstName(firstName);
            a.setLastName(lastName);
            authorRepository.save(a);
        });

        return author;
    }

    public boolean deleteAuthor(Long id) {
        authorRepository.deleteById(id);
        return true;
    }
}

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 is nothing new and exiting:

package de.sandstorm.springboot.graphqldemo.model;

// import ...

import javax.persistence.*;

@Entity
@Getter @Setter @NoArgsConstructor
public class Article {

    // ...

    @ManyToOne
    @JoinColumn(name = "author_id", nullable = false)
    private Author author;

    public Article(String title, String text, Author author) {
        this.title = title;
        this.text = text;
        this.author = author;
    }

}

The new things here are the annotations from javax.persistence, namely @ManyToOne and @JoinColumn. These are informations regarding the JPA library.

For complex fields – like author here – getter and setter are not enough, though. We have 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:

package de.sandstorm.springboot.graphqldemo.resolver;

import com.coxautodev.graphql.tools.GraphQLResolver;
import de.sandstorm.springboot.graphqldemo.model.Article;
import de.sandstorm.springboot.graphqldemo.model.Author;
import de.sandstorm.springboot.graphqldemo.repository.AuthorRepository;
import org.springframework.stereotype.Component;

@Component
public class ArticleResolver implements GraphQLResolver {

    private AuthorRepository authorRepository;

    public ArticleResolver(AuthorRepository authorRepository) {
        this.authorRepository = authorRepository;
    }

    public Author getAuthor(Article article) {
        return authorRepository.findById(article.getAuthor().getId()).orElse(null);
    }
}

Restart your server and everything should work find. 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

### Article.graphqls
type Article {
    id: ID!
    title: String!
    text: String!
    author: Author!
}

extend type Query {
    article(id: ID!): Article
    findAllArticles: [Article]!
}

extend type Mutation {
    newArticle(title: String!, text: String!, authorId: ID!): Article!
    updateArticle(id: ID!, title: String!, text: String!, authorId: ID!): Article
    deleteArticle(id: ID): Boolean!
}
### Author.graphqls
type Author {
    id: ID!
    firstName: String
    lastName: String!
}

extend type Query {
    author(id: ID!): Author
    findAllAuthors: [Author]!
}

extend type Mutation {
    newAuthor(firstName: String!, lastName: String!): Author!
    updateAuthor(id: ID!, firstName: String!, lastName: String!): Author
    deleteAuthor(id: ID): Boolean!
}

That leaves us with a nice and tidy schema.graphqls:

type Query { }
type Mutation { }

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.

The future of this weblog and podcasting about Erlang

The last writing on this blog is almost six years of age. Since then I often visited it myself with an idea about what to write and how to write it. But I never came to turns with myself. Is this a tech blog? Is this a weblog about myself, my ideas? Or is it merely a collection of thoughts thrown out into the wides of the Internet?

I took some sideways myself and ended up where I was just about six years ago: writing software for money and pleasure; thinking about (programming) languages, and nurturing my inner nerd.

My journey continues and it fascinates me, watching the road as it moves under my feet. And that is exactly what this blog will be about: My ideas and thoughts and things that interest me for a moment.

And it will be in English for the time being, since English is more and more my main language to communicate in.

Two weeks ago I was guest at the Cofinpro-Podcast (German). We talked about Erlang and Elixir and the things surrounding concurrency and functional programming.

 

Back to Big

Nachdem ich nun einige Jahre allen in meiner Umgebung erfolgreich vermittelt habe, dass mobile Rechner gar nicht klein genug sein können, gönnte ich mir diese Woche selbst wieder ein 15,4″-Gerät.

Vom ersten Moment an war ich mit dem zusätzlichen Platz sehr zufrieden. Die wenigen Zentimeter helfen tatsächlich, meinen Workflow zu verbessern. Hatte ich bis zuletzt sämtliche Software ausschließlich im Fullscreen-Mode betrieben, kann ich nun Chat, Browser und Terminal wieder sinnvoll nebeneinander betreiben.

Heute Morgen entschloss ich mich, einmal den Versuch zu wagen und mit dem Gerät die Gemütlichkeit der Wohnung zu verlassen. So habe ich mir die 2002 Gramm auf den Rücken geschnallt und bin los gegangen. Erster Eindruck: Dank Rucksack fällt der Unterschied von knapp 700 Gramm zu meinem Vorgängergerät kaum auf. Auch nach über 4 Stunden auf den Beinen im Kampf gegen die Menschenmassen eines Dresdner Nachmittags spürte ich an der Front keine Ermüdungserscheinungen.

Etwas, das allerdings sofort auffällt: Die Tische bei Starbucks, perfekt für eine Tasse Kaffee und ein MacBook Air 13,3″ sind nun etwas zu klein. Beinahe hätte ich die Tasse auf das Notebook gestellt. Der Akku ist nach inzwischen sieben Stunden weg vom Kabel auf 45%. Davon waren aber nur ca. dreieinhalb Stunden tatsächliche Arbeitszeit – inklusive HTML5-Videos und eifriger Kommunikation mit verschiedenen Servern.

Erstes Fazit: Ich denke, das MacBook Pro 15″ Retina und ich können echte Freunde werden.