In the previous tutorial, I introduced you to GraphQL, the problems that GraphQL solved for the limitations of RESTful Web Service. I also gave you a brief guide on implementing GraphQL’s Query type with Spring GraphQL and Spring Data JPA. In this tutorial, we will learn in more detail how to implement this Query type using Spring GraphQL and Spring Data R2DBC.
First, I will create a Spring Boot project with Web, Spring for GraphQL, Spring Data R2DBC, and PostgreSQL Driver as an example:
Result:
As I said in the previous post, to work with GraphQL, we need to define schema .graphqls files. By default, Spring Boot will scan all directories in the project’s classpath src/main/resources to read these schema files.
I will create a folder called “graphql” in the src/main/resources folder and put the GraphQL schema files in this folder.
As an example, I created a new schema.graphqls file to query all students in the database as follows::
1 2 3 4 5 6 7 8 |
type Query { students: [Student] } type Student { id: ID, name: String } |
To handle the above Query, first, I will define the Repository class for the “student” table:
1 2 3 4 5 |
CREATE TABLE student ( id bigint NOT NULL, name varchar(50) NOT NULL, PRIMARY KEY (id) ) |
as follows:
1 2 3 4 5 6 7 |
package com.huongdanjava.graphql; import org.springframework.data.r2dbc.repository.R2dbcRepository; public interface StudentRepository extends R2dbcRepository<Student, Long> { } |
with Student model with the following content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package com.huongdanjava.graphql; import org.springframework.data.annotation.Id; import org.springframework.data.relational.core.mapping.Table; @Table public class Student { @Id private Long id; private String name; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } |
Next, we will create a new controller and define a method with the same name as the field name of the above Query type, as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package com.huongdanjava.graphql; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.graphql.data.method.annotation.QueryMapping; import org.springframework.stereotype.Controller; import reactor.core.publisher.Flux; @Controller public class GraphQLController { @Autowired private StudentRepository studentRepository; @QueryMapping public Flux<Student> students() { return studentRepository.findAll(); } } |
As you can see, the students() method has been annotated with the @QueryMapping annotation so that Spring can scan and map this method name with the field of type Query. Very simple, right?
Behind the sense, the @QueryMapping annotation is implemented using a generic annotation @SchemaMapping:
This @SchemaMapping annotation defines a property typeName which allows us to define the type of GraphQL. Here, our type is Query!
Another property of the @SchemaMapping annotation is the field, which allows Spring to map the field of GraphQL with the name of the method that will handle the request:
By default, if we do not declare a value for this field attribute, its value will be the method name!
If we don’t use the @QueryMapping annotation, we can rewrite the code above using the @SchemaMapping annotation as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package com.huongdanjava.graphql; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.graphql.data.method.annotation.SchemaMapping; import org.springframework.stereotype.Controller; import reactor.core.publisher.Flux; @Controller public class GraphQLController { @Autowired private StudentRepository studentRepository; @SchemaMapping(typeName = "Query", field = "students") public Flux<Student> getAllStudents() { return studentRepository.findAll(); } } |
For testing convenience, we will add the spring.graphql.graphiql.enabled=true property to enable the use of the GraphiQL tool, in the application.properties file:
1 |
spring.graphql.graphiql.enabled=true |
The database information is also configured in the application.properties file as follows:
1 2 3 |
spring.r2dbc.url=r2dbc:postgresql://localhost:5432/student spring.r2dbc.username=khanh spring.r2dbc.password=1 |
My database has the following students:
Now, if you run this application and query using the “students” field above, you will see the following results:
To pass parameters to a Query, we can define an example like this:
1 2 3 4 5 6 7 8 9 |
type Query { students: [Student] studentById(id: Int): Student } type Student { id: ID, name: String } |
In the controller, we will use the @Argument annotation to bind the parameter defined in the Query field with the parameter in the request handle method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package com.huongdanjava.graphql; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.graphql.data.method.annotation.Argument; import org.springframework.graphql.data.method.annotation.QueryMapping; import org.springframework.graphql.data.method.annotation.SchemaMapping; import org.springframework.stereotype.Controller; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @Controller public class GraphQLController { @Autowired private StudentRepository studentRepository; @SchemaMapping(typeName = "Query", field = "students") public Flux<Student> getAllStudents() { return studentRepository.findAll(); } @QueryMapping public Mono<Student> studentById(@Argument("id") Long id) { return studentRepository.findById(id); } } |
The name attribute of this @Argument annotation allows us to specify which parameters in the method are bound to which parameters in the Query.
Result when we query:
1 2 3 4 5 6 |
{ studentById(id: 1) { id name } } |
as follows: