GraphQL Introduction
GraphQL is a query language for the APIs, and a server-side runtime for executing queries using a type system defined for the data. GraphQL isn't tied to any specific database or storage engine and there are multiple libraries to help implement GraphQL in different languages.
In GraphQL, schema is used to describe the shape of available data. Schema defines a hierarchy of types with fields. The schema also specifies exactly which queries and mutations are available for clients to execute. We will look into details on schema in the next section.
Typically when using a REST API we will always get back a complete dataset. Referred as over-fetching we could not limit the fields REST API returns. In GraphQL, using the query language the request can be customized to what is needed down to specific fields within each entity. Once we limit the data the amount of processing needed is reduced and it starts to add up.
Some best practice recommendations for GraphQL services.
- GraphQL is typically served over HTTP via a single endpoint which expresses the full set of capabilities of the service. This is in contrast to REST APIs which expose a suite of URLs each of which expose a single resource.
- GraphQL services typically respond using JSON, however the GraphQL spec does not require it
- While there's nothing that prevents a GraphQL service from being versioned just like any other REST API, GraphQL takes a strong opinion on avoiding versioning by providing the tools for the continuous evolution of a GraphQL schema.
- The GraphQL type system allows for some fields to return lists of values, but leaves the pagination of longer lists of values up to the API designer.
GraphQL Schema Introduction
Let us start with an example and understand the concepts.
# Movie type has fields id, name and Director type Movie { name: String! director: Director } # Director type has fields id, name and list of movies type Director { name: String! movies: [Movie] # List of movies } type Query { movies: [Movie] directors: [Director] } type Mutation { addMovie(id: ID, name: String, director: String): Movie }In this example, we have two object types Movie and Director. Movie has the associated Director and the Director has the list of movies. Relationships are defined in a unified schema, which allows client developers to see what data is available and then request a specific subset of that data with a single optimized query.
- Most of the types defined in a GraphQL schema are object types. An object type contains a collection of fields, each of which has its own type.
- GraphQL's default scalar types are Int, String, Float, Boolean, ID. ID is typically a key for a cache
- A field can return a list containing items of a particular type. This is indicated with [] square brackets
- Non-nullability of a field can be specified with an exclamation mark !
- The Query type is a special object type that defines all of the top-level entry points for queries that clients execute against the server
- The Mutation type is similar to the Query type and defines entry points for write operations.
- Input types are special object types that allow to provider data as arguments to fields as against flat scalar arguments
MovieService example
GraphQL Schema
In this schema we have "Movie" as object type. We define an input type "MovieInput" which helps in the mutation case to pass the object as whole. We also have "Query" and "Mutation" types to get movie(s) and add a movie.
type Movie { id: ID name: String director: String } input MovieInput { id: ID! name: String! director: String! } type Query { movies: [Movie] movieById(id: ID): Movie } type Mutation { addMovie(movieInput: MovieInput): Movie }
Few additional points to note in the schema definition.
- In the "movies" query we have not included any parenthesis since there are no arguments
- The "!" for fields in MovieInput type indicates these are non-nullable and input is expected
Spring Boot dependencies
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-spring-boot-starter</artifactId> <version>5.0.2</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-java-tools</artifactId> <version>5.2.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphiql-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> </dependencies>
Configure application.properties
spring.datasource.url=jdbc:h2:mem:moviesdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
Define the data object
package com.stackstalk; import javax.persistence.Entity; import javax.persistence.Id; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Entity @Data @AllArgsConstructor @NoArgsConstructor public class Movie { @Id private Integer id; private String name; private String director; }
Define the repository
package com.stackstalk; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface MovieRepository extends JpaRepository<Movie, Integer> { }
Define the GraphQL query resolver
package com.stackstalk; import java.util.List; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.coxautodev.graphql.tools.GraphQLQueryResolver; @Component public class MovieQueryResolver implements GraphQLQueryResolver { @Autowired private MovieRepository movieRepository; public Optional<Movie> movieById(Integer id) { return movieRepository.findById(id); } public List<Movie> movies() { return movieRepository.findAll(); } }
Define the GraphQL mutation resolver
package com.stackstalk; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.coxautodev.graphql.tools.GraphQLMutationResolver; @Component public class MovieMutationResolver implements GraphQLMutationResolver { @Autowired private MovieRepository movieRepository; public Movie addMovie(Movie input) { Movie movie = new Movie(input.getId(), input.getName(), input.getDirector()); return movieRepository.save(movie); } }
Define the application
package com.stackstalk; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class MovieServiceApplication { public static void main(String[] args) { SpringApplication.run(MovieServiceApplication.class, args); } }
Putting it all together and testing
Now, let us test the application and examine.Using the GraphQL IDE
In the project dependencies we have already included the GraphiQL dependency. This is an IDE which can be leveraged to perform GraphQL queries and do the mutuations. To launch the IDE use this URL http://localhost:8080/ in the browser.Add a Movie
This mutation is used to add a new movie.GraphQL query
mutation { addMovie(movieInput: { id: 1 name: "Scream" director: "Matt Bettinelli-Olpin, Tyler Gillett" }) { id name director } }Output
{ "data": { "addMovie": { "id": "1", "name": "Scream", "director": "Matt Bettinelli-Olpin, Tyler Gillett" } } }
Query a movie by identifier
This query is used to get a specific movie by identifier.GraphQL query
query { movieById(id: 1) { id name director } }Output
{ "data": { "movieById": { "id": "1", "name": "Scream", "director": "Matt Bettinelli-Olpin, Tyler Gillett" } } }
Query all movies
This query is used to get a list of all movies.GraphQL query
query { movies { id name director } }Output
{ "data": { "movies": [ { "id": "1", "name": "Scream", "director": "Matt Bettinelli-Olpin, Tyler Gillett" }, { "id": "2", "name": "Spider-Man: No Way Home", "director": "Jon Watts " } ] } }Get access to the full source code of this example in GitHub.
In conclusion, typically when using a REST API we will always get back a complete dataset. Referred as over-fetching we could not limit the fields REST API returns. In GraphQL, using the query language the request can be customized to what is needed down to specific fields within each entity. With Spring Boot it is pretty easy to implement GraphQL with the available library support.
Refer to this link for more Java Tutorials.
0 comments:
Post a Comment