1. Introduction

GraphQL is a query language for your API and an alternative to REST APIs. It’s been developer by Facebook and later open sourced. It is now maintained by the GraphQL Foundation.

A difference between GraphQL and REST is that with GraphQL you can expose an API that can be queried by the client. The client can decide what fields it wants to retrieve compared to REST where the server decided on the format that is returned.

We’ll be creating a GraphQL application with Spring Boot 3.1 and a Postgres database. We’ll show how to retrieve data and how to send data in GraphQL.

 

2. Creating GraphQL Service

2.1 Creating the project

For the project setup we use Spring Initializr at https://start.spring.io/ For this project we want to use the following settings.  

Spring Boot: 3.1

Language: Kotlin (or Java if you prefer)

Dependencies:

  • Spring for GraphQL
  • Spring Web
  • Spring Data JDBC
  • PostgresSQL Driver

Project Setup

Now you can Generate the project and open it in your favourite IDE.

 

2.2 Setup Database

We want to add a Postgres docker container that starts with our application. With Spring Boot 3.1 there’s a new feature where Spring can retrieve information from docker-compose files to connect to a database.

We first add a dependency to our Gradle file to get support for this: developmentOnly 'org.springframework.boot:spring-boot-docker-compose'. Add this to your dependencies.

Create a docker-compose.yml at the root of your project graphql with the following content:

version: '3.8'  
  
services:
  postgres:
    image: postgres:15.3
    restart: always
    environment:
      - POSTGRES_USER=graphql
      - POSTGRES_DB=consoles
      - POSTGRES_PASSWORD=password
    ports:
      - '5432:5432'

Because of the dependency we created on spring-boot-docker-compose Spring will retrieve the details to connect to the Postgres database with username: graphql and password: password (don’t do this at home).

 

2.3 Add data to database

Once you’ve opened the project we’ll start by creating the database and filling it with some information. We’re creating a table for gaming consoles and fill it with some consoles.

We start by creating a schema.sql and data.sql file under main > resources. For the schema.sql We will it with the following information.

create table if not exists console  
(  
	id serial primary key,  
	brand text not null,  
	model text not null,  
	year integer not null  
);

This will give us a table console with the columns: id, brand, model and year.

To fill this table with some data we can use the data.sql file. I created some values that can be imported

insert into console(brand, model, year)  
values ('Nintendo', 'NES', 1983);  
  
insert into console(brand, model, year)  
values ('Nintendo', 'SNES', 1990);  
  
insert into console(brand, model, year)  
values ('Nintendo', 'Gameboy', 1989);  
  
insert into console(brand, model, year)  
values ('Nintendo', 'Switch (OLED)', 2020);  
  
insert into console(brand, model, year)  
values ('Playstation', 'PS', 1994);  
  
insert into console(brand, model, year)  
values ('Playstation', 'PS2', 2000);  
  
insert into console(brand, model, year)  
values ('Playstation', 'PS3', 2006);  
  
insert into console(brand, model, year)  
values ('Playstation', 'PS4', 2013);  
  
insert into console(brand, model, year)  
values ('Playstation', 'PS5', 2021);  
  
insert into console(brand, model, year)  
values ('Xbox', 'Xbox', 2001);  
  
insert into console(brand, model, year)  
values ('Xbox', 'Xbox 360', 2005);  
  
insert into console(brand, model, year)  
values ('Xbox', 'Xbox One', 2013);  
  
insert into console(brand, model, year)  
values ('Xbox', 'Xbox Series X', 2020);

(Please don’t sue me Nintendo, for using your name)

There’s one last thing we need to do to make sure the schema.sql and data.sql are picked up. In the application.properties file we need to add: spring.sql.init.mode=always

If we start the application now it will start up the server. Connect to the Postgres database. First it runs the schema.sql file and then data.sql.

 

2.4 Creating the first GraphQL query

Now that we have a database with some data in it, we can start querying the data with GraphQL.

GraphQL uses different types for querying and changing data. We declare a query type of we want to get data and a mutation type if we want to send or change data

To query data we have to set up the Spring code to query data and a contract for GraphQL. Let’s start with the code in Spring.

We first need to declare a data class to represent the Console in the database to code

data class Console(@Id val id: Int? = null, val brand: String, val model: String, val year: Int)

Then we need a Repository class to get the data from the database and afterwards Controller class to expose that data to GraphQL

Since we’re using Spring JDBC we can set up the Repository class in the following way

@Repository
interface ConsolesRepository : CrudRepository<Console, Int> {
}

We now have an Interface that extends CrudRepository and is annotated with @Repository.

For now this is all we need to get all the Consoles.

@Controller
class ConsolesController(val consolesRepository: ConsolesRepository) {
    @QueryMapping
    fun consoles(): Iterable<Console> {
        return consolesRepository.findAll()
    }
}

Notice here the use of QueryMapping this is an annotation for GraphQL like you would use @GetMapping in REST APIs

So we’ve created a Controller class that injects the ConsolesRepository through parameter injection, and we created a function get an Iterable of type Console.

This is all the code we need, now we can declare the GraphQL contract. For this we create a .graphqls file under main > resources > graphql, we’ll call it consoles.graphqls.

In the consoles.graphqls we type the following

type Query {
    consoles: [Console]
}

type Console {
    id: ID!
    brand: String!
    model: String!
    year: Int!
}

We defined a Query type, within the Query type we created a sort of function called consoles which returns a list of type Console. The Type is declared under and it and is in the same representation as the data class Console.

You can see that the consoles name matched the function name we created in the Controller class.

To enable graphiql interface we can add the following to application.properties: spring.graphql.graphiql.enabled=true

   

Now it’s time to start the application.

When you run the application you can go to http://localhost:8080/graphiql. Here you’ll be presented with the Graphiql interface we enabled earlier through the property file.

Now we can start querying. Graphql has autocomplete, so if you start typing “query” it will show the autocomplete options. Our query is called consoles so we can put in

query {
  consoles {
    model
    year
  }
}

type Console {
    id: ID!
    brand: String!
    model: String!
    year: Int!
}

This is one of the benefits of GraphQL, we can define the fields we want it to return. Only the model and year of the console is returned. If you want the ID to be returned you can do:

query {
  consoles {
    id
  }
}

Query consoles

Or if you want all the fields:

query {
  consoles {
    id
    brand
    model
    year
  }
}

 

2.5 Adding parameters to query on

Now that we’ve got the first GraphQL query working, we might want to add values to query on. For example, a year the console was released. @Argument is used to add values to query on.

Let’s add the functionality for that.

First we add the contract to the consoles.graphqls file

type Query {
    consoles: [Console]
    consolesByYear(year: Int): [Console]
}

We’ve added the consolesByYear with the year parameter and, it gives us a list of Console

Now for the ConsolesController we can do the following

@Controller
class ConsolesController(val consolesRepository: ConsolesRepository) {
    @QueryMapping
    fun consoles(): Iterable<Console> {
        return consolesRepository.findAll()
    }

    @QueryMapping
    fun consolesByYear(@Argument year: Int): Iterable<Console> {
        return consolesRepository.findAllByYear(year)
    }
}

We’ve added the consolesByYear function with an @Argument that calls the Repository for all consoles in a certain year.

Changes in the ConsolesRepository

@Repository
interface ConsolesRepository : CrudRepository<Console, Int> {
    fun findAllByYear(year: Int): Iterable<Console>
}

Query consoles

 

3 Final Thoughts

We’ve created a Spring Boot application with a database and a JDBC connection. On top of that we’ve created a Graphql interface to interact without database. If you’re familiar with REST interfaces you can see that not very different, annotating function and adding arguments to them to query the data needed.