GraphQL QuickStart with Spring Boot, Kotlin and MongoDB

Shashir
Geek Culture
Published in
7 min readApr 13, 2021

--

Let’s start with GraphQL Official definition:

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.

Logo’s taken from official sites

In this article, we’ll see what is GraphQL, the benefits of using GraphQL by building a simple GraphQL server with Spring Boot and MongoDB, and its benefits over REST implementation.

With the above official definition, we can understand below points

  • It’s a query language for API’s and a runtime for fulfilling queries
  • It also provides complete description of API data — which helps the client to fetch data as needed.

GraphQL allows us to define the structure and the format of the data we need to fetch and also allows us to query multiple models in a single request.

GraphQL schema will be defined in a file with extension ‘.graphqls’ which contains Schema, Query, Mutation, Object types.

How GraphQL solves REST API drawbacks:

  • GraphQL provides the flexibility to consumers to request for the attributes as needed i.e., it provides dynamic data in request. The REST API response is always fixed, as it sends the entire JSON object for every request.
  • REST API has over-fetching and under-fetching problems.
  • Better performance with lesser code.
  • GraphQL exposes a single endpoint for all the operations with Query and Mutation and its HTTP POST method. In REST, every operation has a separate API using different HTTP verbs(GET, PUT, POST, DELETE) as per the requirement.
  • GraphQL depends on Schema file defined for the application. Only Properties or operations specified on Schema file will be exposed to consumers.

GraphQL Schema:

Schema represents the GraphQL API in terms of Query and Mutation operations along with their types and properties.

schema {
query : GadgetQuery
mutation: GadgetMutation
}

Query Type:

Query type is a root type in GraphQL schema used to create a query to fetch the data from GraphQL server. Its commonly used to define read-only (GET) operations. Let’s consider an example of Gadget gallery application, Query type can be used to define a set of operations as below

Note: Array or List of objects are indicated with a pair of square/box brackets eg: [Gadget] and exclamatory symbol indicates as its mandatory/required property.

Fetch Gadget by GadgetId
Fetch All Gadgets
Fetch Gadgets by Category
Fetch Available Gadgets by Category

## Root type
type GadgetQuery {
application : String
gadget(gadgetId : String!) : GadgetResponse
allGadgets : [GadgetResponse]!
}

Mutation Type:

Mutation type is another root type in GraphQL schema, used to define operations to manipulate data (POST, PUT, DELETE). Let’s consider the same example of Gadget gallery application, Mutation type can be used to define operations like below

type GadgetMutation {
persistGadgetData(gadgetInput : GadgetInput) : GadgetResponse
}

Object Type:

Object type is used to define user-defined types (class in Java/Kotlin) with properties and data types used as an input/output parameter to perform operations with Query and Mutation types as defined in schema file.

Example: Gadget type can be defined in schema file as below.

Note: exclamatory symbol indicates its a mandatory/required parameter. eg: String!

type Gadget {
gadgetId: String!
gadgetName: String!
gadgetCategory: String
gadgetAvailability: Boolean!
gadgetPrice: Float!
}

GraphQL Resolvers:

  • GraphQLQueryResolver:

It’s one of the main resolver used to resolve Query type operations. It acts as a starting point for all read only operations defined within an application (schema file).

GadgetQueryResolver implements ‘GraphQLQueryResolver’ and provide implementation for the query operations defined in schema file. Spring scans package structure for the resolver beans and invoke the right method as declared in schema file.

class GadgetQueryResolver : GraphQLQueryResolver {...}
  • GraphQLMutationResolver:

It’s an another main resolver used to resolve Mutation type operations. It acts as a starting point for all alter data operations defined in GraphQL schema file (i.e., POST, PUT and DELETE operations) .

GadgetMutationResolver class implements ‘GraphQLMutationResolver’ and provide implementation for all the mutation methods defined in schema file.

class GadgetMutationResolver : GraphQLMutationResolver {...}
  • GraphQLResolver:

It acts as a generic resolver for all other types (custom user-defined types) in GraphQL schema file. This is especially used when we have multiple model classes linked to each other with some mappings. To identify type it uses generics.

class PhoneTypeResolver : GraphQLResolver<Phone> {...}

GraphQL server: Hands-on example

Technologies/Tools:

IDE: IntelliJ (STS/Eclipse)
Kotlin: 1.4.x
GraphQL & GraphiQL: 5.x.x
Build tool: Maven
Spring Boot: 2.4.x
Database: MongoDB Server (or any other database — H2 or MySQL)

Spring Boot + MongoDB + Kotlin config

Add below GraphQL Maven dependencies

<dependencies>
...
<!-- Enable GraphQL servlet-mapping at URI - '/graphql' -->
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>5.7.0</version>
</dependency>

<!-- GraphQL UI at '/graphiql' by default - GraphQL testing -->
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphiql-spring-boot-starter</artifactId>
<version>5.5.0</version>
</dependency>

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

Application configuration — application.yml

Here we are overriding default graphiql url from “graphiql” to “gadget-ui” and provided MongoDB configuration

## Server port
server:
port: 9997

## Graphiql Mapping
graphiql:
mapping: gadget-ui

## MongoDB configuration (optional - if not provided spring boot takes default values)
spring:
data:
mongodb:
host: localhost
port: 27017
database: srsgadgetdb

Mongo repository — to connect MongoDB server

@Repository
interface GadgetRepository : MongoRepository<Gadget?, String?> { }

Let’s see full GraphQL schema for Gadget gallery application ‘gadget.graphqls’ placed in project classpath.

schema {
query : GadgetQuery
mutation: GadgetMutation
}

## Root type
type GadgetQuery {
application : String
gadget(gadgetId : String!) : GadgetResponse
allGadgets : [GadgetResponse]!
}

## Root type
type GadgetMutation {
persistGadgetData(gadgetInput : GadgetInput) : GadgetResponse
}

type Gadget {
gadgetId: String!
gadgetName: String!
gadgetCategory: String
gadgetAvailability: Boolean!
gadgetPrice: Float!
}

type GadgetResponse {
gadgetId: String!
gadgetName: String!
gadgetCategory: String
gadgetAvailability: Boolean!
gadgetPrice: Float!
}

input GadgetInput {
gadgetName: String!
gadgetCategory: String
gadgetAvailability: Boolean!
gadgetPrice: Float!
}

Gadget collection and document details with set of properties

@Document(collection = "Gadget")
data class Gadget(

@Id
val gadgetId: String? = null,
var gadgetName: String? = null,
var gadgetCategory: String? = null,
var gadgetAvailability: Boolean? = true,
var gadgetPrice: Double? = null
)

GadgetQueryResolver:

In this class, we provide implementations for the operations defined in schema file for Query root type.

@Component
class GadgetQueryResolver : GraphQLQueryResolver {

@Autowired
var gadgetRepository: GadgetRepository? = null

fun
application() : String {
return "Welcome to GraphQL with Kotlin and MongoDB...!"
}

fun getGadget(gadgetId: String): GadgetResponse? {
var gadget = gadgetRepository?.findById(gadgetId)?.get()
return mapGadgetToGadgetResponse(gadget)
}

fun getAllGadgets(): List<GadgetResponse?>? {
return getGadgetResponse(gadgetRepository?.findAll() as List<Gadget>)
}

private fun getGadgetResponse(gadgetList: List<Gadget>): List<GadgetResponse>? {
val gadgetResponseList: MutableList<GadgetResponse> = ArrayList()
gadgetList.stream().forEach { gadget: Gadget ->
gadgetResponseList.add(mapGadgetToGadgetResponse(gadget))
}
return
gadgetResponseList
}

private fun mapGadgetToGadgetResponse (gadget : Gadget?) : GadgetResponse {
var gadgetResponse = GadgetResponse()
gadgetResponse.gadgetId = gadget?.gadgetId
gadgetResponse.gadgetName = gadget?.gadgetName
gadgetResponse.gadgetCategory = gadget?.gadgetCategory
gadgetResponse.gadgetPrice = gadget?.gadgetPrice
gadgetResponse.gadgetAvailability = gadget?.gadgetAvailability
return
gadgetResponse;
}
}

GadgetMutationResolver:

In this class, we provide implementations for the operations defined in schema file for Mutation type.

@Component
class GadgetMutationResolver : GraphQLMutationResolver {

@Autowired
var gadgetRepository: GadgetRepository? = null

fun
persistGadgetData(persistGadget: GadgetInput): GadgetResponse {
val saveGadgetData = Gadget()
saveGadgetData.gadgetName = persistGadget.gadgetName
saveGadgetData.gadgetCategory = persistGadget.gadgetCategory
saveGadgetData.gadgetPrice = persistGadget.gadgetPrice
saveGadgetData.gadgetAvailability = persistGadget.gadgetAvailability
val
persistedGadget: Gadget? = gadgetRepository?.save(saveGadgetData)
return mapGadgetToGadgetResponse(persistedGadget)
}

private fun mapGadgetToGadgetResponse (gadget : Gadget?) : GadgetResponse {
var gadgetResponse = GadgetResponse()
gadgetResponse.gadgetId = gadget?.gadgetId
gadgetResponse.gadgetName = gadget?.gadgetName
gadgetResponse.gadgetCategory = gadget?.gadgetCategory
gadgetResponse.gadgetPrice = gadget?.gadgetPrice
gadgetResponse.gadgetAvailability = gadget?.gadgetAvailability
return
gadgetResponse;
}
}

GraphQL — Gadget App testing:

By default, GraphQL servlet mapping will be as below
http://localhost:9997/graphiql

Custom URL based on configuration provided in application.yml file : http://localhost:9997/gadget-ui

Testing Gadget Mutation:

Testing Gadget Queries:

Verify MongoDB database for Gadget collection, where we can find 3 documents as below:

That’s it.. I hope you’ve found this article helpful in getting started with GraphQL. In this article, we started with the basics of GraphQL and how GraphQL can be used to implement Query and Mutation with an example to persist and read data from MongoDB.

The complete code for the above example can be found on GitHub

GraphQL official documentation:

--

--

Shashir
Geek Culture

Middleware Chapter Lead & Java Developer — Java/Python/Spring/Microservices/Kafka/Kubernetes(K8S) https://www.linkedin.com/in/shashi999