GraphQL, MongoDB and Java: An introduction

TLDR; Follow the guide below if you are interested in an up to date walkthrough on how to integrate GraphQL, MongoDB and Java - if not head over to my GitHub to view the code.

I decided to create this article as although a lot of guides exist today they were either incomplete, irrelevant or out-dated. After struggling to decipher existing tutorials I thought I would contribute my findings back in hope someone would find them useful…

Background

I wanted to build a GraphQL API for a showcase project I am currently working through. Alongside GraphQL the API will run under Spring Boot (to simplify the process of creating and running a Spring application) this will then connect to a simple MongoDB instance.

Now we know the why let’s explore the how!

MongoDB

As previously stated I want the application to connect to a MongoDB instance. For this tutorial I will be running MongoDB via docker-compose. If you don’t have docker and docker-compose installed then head over here for help on getting started. Alternatively, if you want to run on your own MongoDB instance then that’s fine :) (although the rest of this section will go into detail on setting up a Docker MongoDB instance).

Our compose file looks like the following:

version: '3'

services:
  mongo:
  image: mongo
    env_file:
    - mongo.env
    ports:
    - 27017:27017
    volumes:
    - ./data/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js

Nothing special here although for those following closely there are 2 things to note:

Dot Env

I make use of dot env to remove the need to store security information in our configuration files as defined as best practice in 12 Factor Apps. For the purpose of this tutorial the values stored in the dot env file are provided below.

MONGO_INITDB_ROOT_USERNAME=root
MONGO_INITDB_ROOT_PASSWORD=example
MONGO_INITDB_DATABASE=admin
MONGO_HOST=mongo

The file configures the initial username and password to authenticate against the DB, the name of the initial database where the user will be inserted and the hostname to connect to the MongoDB server (if I are running our GraphQL app via Docker).

Volume

Finally, I have added a volume which copies an initiailsation script into the docker-entrypoint-initdb.d directory. This script is used to bootstrap the application, a snippet of it is below:

// Auth against admin DB
db.auth('root', 'example')

// Change to desired DB
db = db.getSiblingDB('graphql-app')

// Clear previous dummy data
db.users.drop();
db.articles.drop();

// Insert dummy data
db.articles.insertMany([...

Essentially, it:

  • Authenticates against the database
  • Selects the database I want to create for our test application
  • Clears any existing data
  • Creates and inserts dummy data into 2 collections - Users and Articles

Application

Now that I have our database up and running its time to look into the actual application. As a reminder this will be a Spring Boot application that creates a GraphQL server that will query the previously created MongoDB database.

Pom.xml

Our application utilises Maven for its dependency management. A key aspect of this guide is to highlight the required dependencies:

<!-- GraphQL -->
<dependency>
  <groupId>com.graphql-java</groupId>
  <artifactId>graphql-java</artifactId>
  <version>11.0</version>
</dependency>

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

The above dependencies are required to add GraphQL support to our project. One of the main aspects to notice here is that the graphql-java-tools dependency is now published under com.graphql-java-kickstart. This was a change that occurred in September 2018 but is not reflected in many guides.

<!-- Spring Tools -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

<dependency>
  <groupId>com.graphql-java-kickstart</groupId>
  <artifactId>graphql-spring-boot-starter</artifactId>
  <version>${graphQL.helper.version}</version>
</dependency>

<dependency>
  <groupId>com.graphql-java-kickstart</groupId>
  <artifactId>graphiql-spring-boot-starter</artifactId>
  <version>${graphQL.helper.version}</version>
  <scope>runtime</scope>
</dependency>

Here I add the Spring Boot and MongoDB starters respectively which configures our application as a Spring Boot app. It also adds the required libraries for working with MongoDB. The final two dependencies wire up our application to serve our GraphQL app and GraphiQL (an in browser IDE for querying GraphQL). Again these dependecies were transferred to a new repository in September 2018.

<!-- Data tools -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
</dependency>

Finally, I add the awesome Lombok to reduce the amount of boilerplate code I need to write.

Phew, that’s all of the required dependencies added and explained!

Spring Boot

In this project I make use of Spring Boot 2. I configure the application via an application.yml file located in the projects resource directory (you can also use an application.properties file if you so wish). Our application.yml file looks like this:

spring:
	data:
  	mongodb:
    	database: graphql-app
      port: 27017
      host: localhost
      username: ${MONGO_USERNAME:root}
      password: ${MONGO_PASSWORD:example}
      authentication-database: ${MONGO_AUTH:admin} # Our user was added in the Admin DB
  server:
    port: 9000
graphql:
  servlet:
  	enabled: true
graphiql:
	enabled: true

Again nothing too special here. The main thing to note is that I configure access to our MongoDB instance via environment variables or with a default value if such an environment variable is not provided.

Graphqls

Rather than defining our schema in code I want to make use of SDL (Schema Definition Language) to define our GraphQL API - graphql-java-tools allows us to do just that. I can define .graphqls files that use SDL to describe our desired API. These files are then stored in the projects resources directory. A snippet of these definition files is provided below:

type Query {
  users: [User]
  user(id: ID!): User
}

type User {
  id: ID!
  name: String!
  age: Int!
  nationality: String
  createdAt: String!
  articles: [Article]
}

Here I define a Query type that details the search endpoints I want to be available in our GraphQL API. I also define a User type that maps to our users MongoDB collection. An important note here is that a User can be the author of zero or more articles (which is detailed later on).

Entities

I create POJO’s (Plain Old Java Objects) to represent our MongoDB collections. An example POJO for the User entity is provided below:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Document(collection = "users")
public class User {
  private String id;
  private String name;
  private Integer age;
  private Date createdAt;
  private String nationality;
  private List<ObjectId> articles;
}

See lombok for more information on the annotations.

The @Document annotation is provided by the Spring Boot MongoDB dependency, it is used to associate the POJO with the appropriate MongoDB collection.

Finally, the eagle-eyed reader might have noticed that I define the id property as a String type although I make use of ObjectID's in the database. The reason for this is that I will run into a mapping exception if I return the id property as part of a query (as it is returned as a String from the database).

Repositories

A repository is used to provide access to the underlying MongoDB collection for querying. In our example these are very simple - I extend the default MongoRepository for our collection which provides access to methods such as findById etc.

@Repository
public interface UserRepository extends MongoRepository<User, ObjectId> {}

In the above snippet the first property in the type definition is the POJO class corresponding to this repository, the second property is the data type of the key for that collection.

Queries

In our schema definition I defined the following query:

user(id: ID!): User

The above defines an endpoint (/user) that requires a non-nullable parameter of type ID. On completion either a null value (as indicated by the lack of !) or a response of type User is returned. The corresponding implementation for this endpoint looks like:

@RequiredArgsConstructor
@Component
public class UserQueries implements GraphQLQueryResolver {
  private final UserRepository userRepository;

  public Optional<com.aa.graphql.entities.User> getUser(ObjectId id) {
    return userRepository.findById(id);
  }
}

This implements the query method as it:

  • Returns an Optional (nullable) User type
  • Takes in a an ID parameter of type ObjectID
  • Uses the MongoDB method findById to find the appropriate User in the database

I also implement the GraphQLQueryResolver interface to signify that I have implemented the Query method defined in our schema definition.

Resolvers

The final part of our tutorial covers the scenario where there is a requirement to provide more complex data structures as part of the query response (i.e. lookup a value from another collection). For this I need to implement a GraphQLResolver. The resolver is typically the name of the POJO class with Resolver on the end (In our example below the file is named UserResolver). Hopefully you remember that the User type has a complex property that returns an Array of articles associated to that user:

articles: [Article]

This can’t be queried from the single collection so I need to implement a resolver to retrieve the relevant data.

@RequiredArgsConstructor
@Component
public class UserResolver implements GraphQLResolver<User> {
  private final ArticleRepository articleRepository;

  public Iterable<Article> getArticles(User user)
  {
    return articleRepository.findAllById(user.getArticles());
  }
}

The resolver is only invoked when a query retrieves Articles for a User. When a User record is found, that object is passed into the resolver (as denoted by GraphQLResolver<User>). As I now have the data for the desired user available to us I can get the value(s) stored in the articles property (a list of article ObjectId’s) and use them to retrieve the desired Article’s information.

Code

A complete sample project can be found on my GitHub repository. The project contains instructions on how to run the project locally for those of you that would like to play around with the code.

Conclusion

As I stated at the beginning of this article I aimed to provide a detailed and up-to-date walkthrough outlining how to integrate Java, GraphQL and MongoDB. Hopefully you have found this guide interesting and informative - or at least you enjoyed playing around with the source code!