Creating APIs using GraphQL

In this article we will see how to create APIs using Spring Boot GraphQL.

Imagine a scenario where an endpoint returns data for a Student, where the response payload contains 50 fields performing multiple queries on the server for a client consuming on a web application, which can contain academic credentials, address, marks, and much more.

Now at certain point in the application we need just the academic credentials of the student. If we use the same endpoint then we running queries that are not even required and impacting the performance.

This is where GraphQL comes into picture. GraphQL gives the power to the client to decide what data it wants from a particular endpoint, and the same shall be served, nothing more, nothing less.

Set up the Spring Boot Application

Go to start.spring.io . Generate a Java project without any dependencies. We will add the dependencies manually. You can choose maven or gradle, but for this article, we are going with maven

Note: The spring version for this project is 2.6.3

Lets update our pom.xml file

Add the following properties so that we can directly reference them

pom.xml

.
.
<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <graphql.spring.starter.version>11.1.0</graphql.spring.starter.version>
        <h2.version>1.4.200</h2.version>
        <lombok.version>1.18.20</lombok.version>
        <java.version>11</java.version>
</properties>
.
.

Next, we will add the following dependencies

  1. Web
  2. H2 Embedded database
  3. Lombok
  4. GraphQL
  5. DevTools

pom.xml

.
.
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.graphql-java-kickstart</groupId>
            <artifactId>graphql-spring-boot-starter</artifactId>
            <version>${graphql.spring.starter.version}</version>
        </dependency>
        <dependency> <!-- to embed GraphiQL tool.  See http://localhost:8080/graphiql -->
            <groupId>com.graphql-java-kickstart</groupId>
            <artifactId>graphiql-spring-boot-starter</artifactId>
            <version>${graphql.spring.starter.version}</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>${h2.version}</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.graphql-java-kickstart</groupId>
            <artifactId>graphql-spring-boot-starter-test</artifactId>
            <version>${graphql.spring.starter.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
.
.

Finally, we will add our maven build plugin

pom.xml

.
.
<build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <release>${java.version}</release>
                        <annotationProcessorPaths>
                            <path>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                                <version>${lombok.version}</version>
                            </path>
                            <annotationProcessorPath> <!-- necessary to generate META-INF/spring-configuration-metadata.json -->
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-configuration-processor</artifactId>
                                <version>2.6.3</version>
                            </annotationProcessorPath>
                        </annotationProcessorPaths>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok-maven-plugin</artifactId>
                <version>${lombok.version}.0</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>delombok</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
.
.

Add configurations to application.yml file

Lets add graphql and spring datasource, h2 and jpa configurations and application.yml file.

application.yml

graphql:
  servlet:
    mapping: /graphql

spring:
  datasource:
    url: jdbc:h2:mem:testdb;TRACE_LEVEL_FILE=3
    username: user
    password: pass
    driverClassName: org.h2.Driver
    initialization-mode: embedded
  h2:
    console:
      enabled: true
      path: /h2-console
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: update
    properties.hibernate.jdbc.time_zone: UTC
    show-sql: false

Create a GraphQL schema

Lets create out GraphQL schema for our Student entity. We would be looking at two operations

  1. Query - for fetching data from the server
  2. Mutation - for updating data on the server

Go to src/main/resources and create a graphql directory. Create a student.graphqls.

student.graphqls

type Student {
    id: ID!
    firstName: String!
    lastName: String!
    email: String!
    cgpa: Float!
}

type Query {
    findAllStudents: [Student]!
    findStudentById(studentId:Int!):Student
}

type Mutation {
    addStudent(firstName: String!, lastName: String!, email:String!, cgpa:Float!) : Student!
}

In this findAllStudents and findStudentById will query the database and return all list of all students and a particular student by Id respectively. Here [Student] specifies a list of students will be expected.

Similarly, addStudent shall create a record for a student in the database.

Create a model class for Student

Create a package models and add Student.java class to it. In this, we specify all the properties referring to our graphQL schema.

models/Student.java

@Entity
@Table(name = "students")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Student
{
    @Id
    @Column(nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @Column(nullable = false)
    private String firstName;

    @Column(nullable = false)
    private String lastName;

    @Column(nullable = false)
    private String email;

    @Column(nullable = false)
    private Double cgpa;
}

Create a JPA Repository for Student entity

Create a package repositories and add an interface StudentRepository.java which will extend the JpaRepository

repositories/StudentRepository.java

public interface StudentRepository extends JpaRepository<Student,Integer> {
}

Add Query and Mutation Resolvers

We need to add resolvers to map all the methods that we specified in the schema. Create a package resolvers.

First we add our query methods. In the resolvers package, add a class Query.java. This class will implement the GraphQLQueryResolver.

resolvers/Query.java

public class Query implements GraphQLQueryResolver
{
    private StudentRepository studentRepository;

    public Query(StudentRepository studentRepository)
    {
         this.studentRepository = studentRepository;
    }

    public Iterable<Student> findAllStudents()
    {
         return studentRepository.findAll();
    }

    public Student findStudentById(Integer studentId)
    {
         return studentRepository.findById(studentId).get();
    }
}

Next, add a Mutation.java class which will implement GraphQLMutationResolver

resolvers/Mutation.java

public class Mutation implements GraphQLMutationResolver
{
    private StudentRepository studentRepository;

    public Mutation(StudentRepository studentRepository) {
        this.studentRepository = studentRepository;
    }

    public Student addStudent(String firstName, String lastName, String email, Double cgpa)
    {
         Student student = new Student();
         student.setFirstName(firstName);
         student.setLastName(lastName);
         student.setEmail(email);
         student.setCgpa(cgpa);

         Student savedStudent = studentRepository.save(student);
         return savedStudent;
    }
}

Add some test data

Lets implement the CommandLineRunner interface to add some test data in our database when the application runs to test our queries.

Create a TestData.java class which will implement the CommandLineRunner interface.

TestData.java

@Component
public class TestData implements CommandLineRunner {

    @Autowired
    private StudentRepository studentRepository;

    @Override
    public void run(String... args) throws Exception
    {
        Student student1 = new Student();
        Student student2 = new Student();
        Student student3 = new Student();

        student1.setFirstName("Harsh");
        student1.setLastName("Pal");
        student1.setEmail("hp@testmail.com");
        student1.setCgpa(8.5);

        student2.setFirstName("Abhinav");
        student2.setLastName("Kumar");
        student2.setEmail("ak@testmail.com");
        student2.setCgpa(8.2);

        student3.setFirstName("Himanshu");
        student3.setLastName("Nayak");
        student3.setEmail("hn@testmail.com");
        student3.setCgpa(8.0);

        studentRepository.save(student1);
        studentRepository.save(student2);
        studentRepository.save(student3);
    }
}

Lets run some queries

Start the spring boot applcation. Access the web console for our H2 database at localhost:8080/h2-console

Give the username and password as specifies in application.yml file

username : user
password : pass

Run the query

SELECT * FROM STUDENTS

to see the sample records inserted in the database.

Now lets open GraphiQL, which is an in-browser IDE for exploring GraphQL queries. Go to localhost:8080/graphiql

  1. Lets run our query to get only first name and email of all students

query 1

{
   findAllStudents {
    firstName
    email
  }
}

The response will be like this.

response 1

{
  "data": {
    "findAllStudents": [
      {
        "firstName": "Harsh",
        "email": "hp@testmail.com"
      },
      {
        "firstName": "Abhinav",
        "email": "ak@testmail.com"
      },
      {
        "firstName": "Himanshu",
        "email": "hn@testmail.com"
      }
    ]
  }
}

Lets run a query to get only first name, last name and cgpa of all students.

query 2

{
   findAllStudents {
    firstName
    lastName
    cgpa
  }
}

The response of the following query will be

response 2

{
  "data": {
    "findAllStudents": [
      {
        "firstName": "Harsh",
        "lastName": "Pal",
        "cgpa": 8.5
      },
      {
        "firstName": "Abhinav",
        "lastName": "Kumar",
        "cgpa": 8.2
      },
      {
        "firstName": "Himanshu",
        "lastName": "Nayak",
        "cgpa": 8
      }
    ]
  }
}

Observe how the client has complete control over the data that it wants and it gets precisely that.

Now lets get a particular student by Id and retrieve only Id, email and cgpa for that student.

query 3

{
   findStudentById(studentId : 2) {
    id
    email
    cgpa
  }
}

response 3

{
  "data": {
    "findStudentById": {
      "id": "2",
      "email": "ak@testmail.com",
      "cgpa": 8.2
    }
  }
}

Now lets try to create a record for student in the database. After creating the record, we will fetch only Id , first name and last name for that record.

mutation 4

mutation {
  addStudent(
    firstName:"Puneet", 
    lastName:"Singh", 
    email:"ps@testmail.com", 
    cgpa:8.7)
  {
      id
      firstName
      lastName
  }
}

response 4

{
  "data": {
    "addStudent": {
      "id": "4",
      "firstName": "Puneet",
      "lastName": "Singh"
    }
  }
}

This is how powerful GraphQL is.

  1. Client controls what data it wants
  2. Few number of endpoints can serve a large variety of requests
  3. Performance optimization

I hope you found the article useful.

Lets connect :

Happy Coding :) .