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
- Web
- H2 Embedded database
- Lombok
- GraphQL
- 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
Query
- for fetching data from the serverMutation
- 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
- 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.
- Client controls what data it wants
- Few number of endpoints can serve a large variety of requests
- Performance optimization
I hope you found the article useful.
Lets connect :
Happy Coding :) .