In todays rendition of the summer series we are going to have a look at a technology I have been trying to avoid for quite a while now: GraphQL. I have always seen it as more of a hack than anything else. Maybe that’s because I have thought of it as “legal” SQL injection or maybe it’s because, to be honest, GraphQL looks kinda scary at first.

Anyways…today, I am going to write a simple API with GraphQL in Spring Boot and will attempt to answer some questions:

  • What is GraphQL?
  • How do I develop a GraphQL API in Spring Boot?
  • Is it faster than REST?

I will write a simple “User-Item” application and develop both a classic REST API and a GraphQL one. To benchmark this I will do four tests (I’ll do them 1000 times and measure the execution time):

  • Request all users with all items
  • Request a single user with all items
  • Request two users with all items
  • Request four users with all items

For the database I will be using Couchbase (who would’ve guessed??).

Okay but wth is GraphQL

Nowadays, we usually use REST as the central programming paradigm for APIs. In REST, we utilize HTTP methods to manipulate (Create, Read, Update, Delete) single elements. We can’t really influence the way we receive our data or how we receive it. We just get several endpoints we have to work with and there’s that.

So…according to the official GraphQL website, GraphQL is a “query language for your API”. It’s a programming paradigm that doesn’t aim to replace REST, but rather provide a good alternative to it. GraphQL allows you to create a schema which defines the datatypes and structure of your query. It also allows you to define how your requested data is returned, expose methods and even return and/or manipulate multiple elements in one request.

While this all sounds a bit like SOAP (thank god, that that’s dead), it is in no way as complicated, convoluted or as horrible as SOAP was and I’d argue it’s even easier than REST, in some cases.

So a GraphQL query could look like so:

{
  hero(name: "R2-D2") {
    name
    friends {
      name
    }
  }
}

With the response looking like this:

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

The backend 🍃 🛋️

We’re going to start our database in docker like so:

docker run -d --name cb -p 8091-8094:8091-8094 -p 11210:11210 -v ~/couchbase:/opt/couchbase/var couchbase

I don’t think I need to go over my configuration here to be honest…it’s the same as always: some API stuff, dev tools, DB driver, etc.

As soon as we have our project initialized and loaded, we are going to add three dependencies to our pom.xml.

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

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

<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphiql-spring-boot-starter</artifactId>
    <version>5.0.2</version>
</dependency>
  • graphql-spring-boot-starter: A simple, plug & play, GraphQL server for Spring Boot.
  • graphql-java-tools: Helps us to wire all the GraphQL stuff up.
  • graphiql-spring-boot-starter: An embeddable web app to test our API with.

Next, let’s create our GraphQL schema in src/main/resources
/graphqldemo.graphqls (the filename corresponds with your applications artifactId). For now, let’s just add a basic schema. The Query type will be our query root.

type Item {
    id: ID!,
    name: String!,
    description: String
}

type User {
    id: ID!,
    username: String!,
    items: [Item]
}

type Query {
    users: [User]
}

Now that that’s done, let’s build our application around that (the following code is mostly standard Spring…if you’ve ever done something with Spring and Couchbase, skip here). First, we need our database configuration and domain classes.

src/[..]/graphqldemo/configuration
/CouchbaseConfiguration.java

package at.simulevski.graphqldemo.configuration;

import [..];

@Configuration
@EnableCouchbaseRepositories
public class CouchbaseConfiguration extends AbstractCouchbaseConfiguration {

    @Value("${spring.couchbase.host:localhost}")
    private String host;

    @Value("${spring.couchbase.bucketname:users}")
    private String bucketname;

    @Value("${spring.couchbase.username:demo}")
    private String username;

    @Value("${spring.couchbase.password:demo123}")
    private String password;

    @Override
    protected List<String> getBootstrapHosts() {
        return Arrays.asList(host);
    }

    @Override
    protected String getBucketName() {
        return bucketname;
    }

    @Override
    protected String getUsername(){
        return username;
    }

    @Override
    protected String getBucketPassword() {
        return password;
    }
}

src/[..]/graphqldemo/domain

User.java

package at.simulevski.graphqldemo.domain;

import [..]

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter

@Document
@Data
public class User {
    @Id
    private String id;

    @NotNull
    @Field
    private String username;

    @Field
    private List<Item> items = new ArrayList<>();
}

Item.java

package at.simulevski.graphqldemo.domain;

import [..];

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter

@Data
public class Item {
    private String id;
    private String name;
    private String description;
}

To access our data, we need a repository.

src/[..]/graphqldemo
/persistence/UserRepository.java

package at.simulevski.graphqldemo.persistence;

import [..];

public interface UserRepository extends CrudRepository<User, String> {
    @Query("SELECT _class, username, items, META().id AS _ID, META().cas AS _CAS FROM `users` WHERE _class = \"at.simulevski.graphqldemo.domain.User\"")
    List<User> getAll();
}

GraphQL resolver

Now we need to wire everything up to our GraphQL resolver. Since the Query type is our query root, we’re going to create a resolver for that.

src/[..]/graphqldemo/Query.java

package at.simulevski.graphqldemo;

import [..];

@Component
public class Query implements GraphQLQueryResolver {

    @Autowired
    private UserRepository userRepository;

    public List<User> getUsers(){ //This gets automatically picked up by our GraphQL backend
        var result = userRepository.getAll();
        return result;
    }
}

This should actually be everything we need to get our application up and running. So let’s test this!

First, we’re going to manually insert a document into Couchbase.

Screenshot

Remember that our document needs a _class attribute for the Java client to know of what type it is when retrieving it.

Now we can start our actual Spring Boot program and connect to localhost:8080/graphiql. Here, we can test our GraphQL endpoint with a simple query that looks like so:

query{
  users{
    id
    username
  }
} 

Screenshot

Moar filters!

So now that we have a basic GraphQL server running, we can add more filter options so the API becomes a bit more…usable. For this, we need to add a new retrieval method in our query root. So in src/main/resources
/graphqldemo.graphqls, we change the Query type to:

type Query {
    users: [User]
    user(id: ID, username: String): User
    itemFromUser(userId: ID!, itemId: ID!): Item
}

This means that we also have to register the according method in src/[..]/graphqldemo/Query.java.

public User getUser(String id, String username) throws Exception {
    if (id == null && username != null){
        return userRepository.findByUsername(username).orElseThrow();
    } else if (id != null && username == null){
        return userRepository.findById(id).orElseThrow();
    } else{
        throw new Exception("Can't have both an id and a username argument for this search");
    }
}

public Item getItemFromUser(String userId, String itemId){
    return userRepository
        .findById(userId)
        .orElseThrow()
        .getItems()
        .stream()
        .filter(a -> a.getId().equals(itemId))
        .findFirst().orElseThrow();
}

And a method in src/[..]/graphqldemo/persistence
/UserRepository.java to retrieve a user by its username.

Optional<User> findByUsername(String username);

Screenshot

Screenshot

Mutation formation

Now that we can read and filter users with our API, let’s try to get our API to create users.

Again, we have to change our GraphQL schema.

type Mutation {
    createUser(username: String!): User
    createItemForUser(userId: String!, name: String!, description: String!): Item
}

We need another file that defines all the necessary methods for our Mutation query type.

src/[..]/graphqldemo
/Mutation.java

package at.simulevski.graphqldemo;

import [..];

@Component
public class Mutation implements GraphQLMutationResolver {

    @Autowired
    private UserRepository userRepository;

    public User createUser(String username){
        var user = User
                .builder()
                .id(UUID.randomUUID().toString())
                .username(username)
                .build();

        userRepository.save(user);

        return user;
    }

    public Item createItemForUser(String userId, String name, String description){
        var user = userRepository.findById(userId).orElseThrow();
        var items = user.getItems();

        var item = Item
                .builder()
                .id(UUID.randomUUID().toString())
                .name(name)
                .description(description)
                .build();

        items.add(item);
        user.setItems(items);

        userRepository.save(user);

        return item;
    }
}

Now we can actually try this out.

Screenshot

Screenshot

Benchmark time

To benchmark this, we actually need to create a normal controller which can can do the methods we need for our planed benchmarks.

src/[..]/graphqldemo
/presentation
/UserController.java

package at.simulevski.graphqldemo.presentation;

import [..];

@Controller
public class UserController {

    @Autowired
    private UserRepository userRepository;

    @GetMapping("/user/{name}")
    public @ResponseBody User getUsers(String userName){
        return userRepository.findById(userId).orElseThrow();
    }

    @GetMapping("/user")
    public @ResponseBody List<User> getUsers(){
        return userRepository.getAll();
    }
}

I’ll actually write the benchmark in C#…cause why the hell not?

The result is actually exactly what I expected…

Screenshot

As you see, there’s barely any difference in speed here. REST is a bit faster in accessing single and all users, while being slower in accessing multiple users (because one has to make more requests).

Conclusion

GraphQL is a really cool technology that holds a lot of potential. The way to query data is intuitive and allows for some really nice API code. Now…I wouldn’t see GraphQL replacing REST and REST definitely isn’t going anywhere anytime soon, but it’s still very nice to have a good alternative to it (other than SOAP…). I am pretty sure that I won’t be starting to use GraphQL in every future project…but I can see myself using it now and then.

me thinking about SOAP

As always, you can find the entire project on GitHub.