Facebook developed GraphQL in 2012 to solve its problem of mobile communication. Since it was open-sourced in 2015, it has been adopted by companies like Twitter, Shopify, Lyft, Airbnb, Github, Yelp, and many more.
There is no doubt that GraphQL adoption is on the rise. The State of JavaScript 2020 report mentions that only 6% of developers surveyed had used it in 2016, however, 47% of developers used it in 2020.
Spring for GraphQL, which recently released version 1.0, provides a higher level abstraction for building the Spring Boot GraphQL service.
If you want a quick overview of GraphQL and how to implement the Spring Boot GraphQL service then you have come to the right place.
Let’s get started!
In this article
What is GraphQL?
GraphQL is a query language and server runtime.
- Query language: GraphQL is a query language for API. In GraphQL, you design API based on its type system. A GraphQL API is expressed as a statically typed schema.
- Server runtime: On the service side, a GraphQL service provides a runtime layer that describes the structure of data exposed by API, and this runtime layer is responsible for parsing GraphQL requests and calling the appropriate data fetcher (also called resolver) for each field.
In short, GraphQL is a query language for API and a server-side runtime for executing queries. Also, it’s not tied to any specific programming language, database, or storage engine.
A GraphQL query to fetch book by an id
{
bookById(id: 4) {
name
author
}
}
Response
{
"data": {
"bookById": {
"name": "Atomic Habits",
"author": "James Clear"
}
}
}
Spring Boot GraphQL Service
Features of GraphQL
Some important features of GraphQL are:
Strongly Typed
GraphQL APIs are strongly typed and expressed as a schema. This strongly typed nature makes GraphQL APIs more predictable and discoverable. Additionally, the type system provides other benefits, such as introspection.
Hierarchal Data
As the “Graph” in GraphQL suggests, GraphQL provides first-class support for hierarchical data. With GraphQL, you can create a request as a graph of related fields. The GraphQL response is shaped like the request, a natural way for a client to describe its data requirement.
Client-specific Response
REST APIs are often criticized for over-fetching. Generally, a REST API returns all data under a resource even though you need only a fraction of the data. This is not a problem in GraphQL as a GraphQL client can choose to request data on the field level granularity.
Introspection
Because of the statically typed nature of GraphQL, any client can ‘introspect’ the server and ask for the schema. One popular GraphQL tool that relies on this concept is GraphiQL, a feature-rich browser-based editor to explore and test GraphQL requests.
You can explore some popular GraphiQL interfaces
- Star Wars GraphQL API: az.dev/swapi-graphql
- GitHub GraphQL API: az.dev/github-api
Why GraphQL?
REST is the most popular way of building APIs, but it has many shortcomings. Let’s understand the problem with REST APIs that GraphQL promises to solve.
- Standard: The GraphQL specification is maintained by the GraphQL community, providing a comprehensive standard for developing maintainable APIs. In contrast, REST APIs lack an industry-wide standard despite being around for many years.
- API Documentation: With REST API, API documentation is maintained in OpenAPI spec. However, documentation and implementation can drift apart over time, which is a common issue. In GraphQL, there is no need to maintain separate API documentation.
- Overfetching: Overfetching is a significant issue with REST APIs, particularly for mobile applications. Pure REST APIs are built around resources and lack the concept of partial response. For instance, a mobile e-commerce app displaying order history must show the name, purchase date, and price of a product. However, the
/orders
API of a REST API may return many other fields, such as payment details, discounts, shipment details, etc., making it inefficient for mobile use. - Under fetching: REST APIs are usually fine-grained and built around resources. Thus, a client application may call multiple APIs to construct a view. This is typically not an issue in GraphQL due to its hierarchal nature.
Why Not GraphQL?
However, this doesn’t mean GraphQL is perfect in every sense; it has its own shortcomings. For example:
- As REST API has been around for many years, it has a very well-defined security measure, but the same is not true for GraphQL.
- Compared to REST APIs, implementing client-side caching is very hard in GraphQL.
- Things like rate limiting are harder to implement in GraphQL. This can easily lead to a denial of service attack, as it’s possible to bring down the whole service by requesting deeply nested data.
As with everything in life, choosing between REST and GraphQL involves making trade-offs.
GraphQL Schema
The GraphQL Schema defines how data is requested and returned from the server and is governed by GraphQL Schema Definition Language (SDL). A GraphQL schema may contain operations, variables, and directives.
GraphQL Operation
A GraphQL service can support the following operations
- Queries: Queries represent
READ
operations. - Mutation: Mutation involves
WRITE
thenREAD
operation. - Subscription: A subscription is used for continuous
READ
(for example, over WebSocket).
Schemas and Types
Every GraphQL API defines a set of types that describe the data you can query on that API. A GraphQL request is validated and executed against that schema.
Object Type and Fields
The most fundamental components of a GraphQL schema are object types, which represent an object you can fetch from a service and its corresponding fields. In the GraphQL schema language, you can represent an object type for a Book
as follows:
type Book {
id : ID
name : String
author: String
price: Float
ratings: [Rating]
}
In the above example,
- Type:
Book
is GraphQL object type. - Fields: id, name, author, price, and ratings are fields of
Book
type. - Scalar types: String, Float, and Int are some of the built-in scalar types.
- ID: the ID scalar type represents a unique identifier, often used to refetch an object. Additionally, the ID type is serialized in the same way as a String.
Example of a GraphQL book-catalog API
type Query {
books: [Book]
bookById(id : ID) : Book
}
type Book {
id : ID
name : String
author: String
price: Float
ratings: [Rating]
}
type Rating {
id: ID
rating: Int
comment: String
user: String
}
GraphiQL Editor
One of the reasons GraphQL is very popular is GraphiQL (pronounced “graphical”), an open-source web application (written with React.js and GraphQL) that runs in a browser. The best thing about GraphiQL is that it provides intelligent type-ahead and auto-completion features, which is possible because of GraphQL’s statically typed schema.
Spring Boot GraphQL support
Spring Boot GraphQL provides support for Spring applications built on GraphQL Java. It supports the handling of GraphQL requests over HTTP, WebSocket, and RSocket.
Spring for GraphQL is the successor of the GraphQL Java Spring project from the GraphQL Java team. It aims to be the foundation for all Spring, GraphQL applications.
Server Transport
GraphQL specification itself doesn’t talk anything about transport layer protocol. GraphQL over HTTP specification extends GraphQL specification to cover the topic of serving GraphQL services over HTTP.
As per GraphQL over HTTP specification, requests must use HTTP POST with request details included as JSON in the request body. Once the JSON body has been successfully decoded, the HTTP response status is always 200 (OK), and any errors from GraphQL request execution appear in the “errors” section of the GraphQL response.
The default and preferred choice of the media type is "application/graphql+json"
, but "application/json"
is also supported.
GraphQL Service Implementation
In Spring Boot, you can implement the GaphQL service using low-level GraphQL Java API or a higher-level abstraction as Controller
annotation (similar to REST API). However, if you are starting out then, I think it’s better to start with the low-level GraphQL Java implementation as it covers some important concepts. In this article, we’ll use GraphQL Java APIs rather than Controller
annotation.
The working code example of this article is listed on GitHub . To run the example, clone the repository and import graphql-spring as a project in your favorite IDE as a Gradle
project.
In this code example, we’ll implement the following GraphQL API.
type Query {
books: [Book]
bookById(id : ID) : Book
}
type Book {
id : ID
name : String
author: String
price: Float
ratings: [Rating]
}
type Rating {
id: ID
rating: Int
comment: String
user: String
}
GraphQL Dependency
To implement GraphQL in Spring Boot, the main dependency you need to define is spring-boot-starter-graphql
. For the transport layer, I have added dependency on spring-boot-starter-webflux
. Additionally, you can use Spring MVC, Instead of WebFlux, as Spring provides support for both.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-graphql'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
testImplementation 'org.springframework.graphql:spring-graphql-test'
}
RuntimeWiring
GraphQL Java RuntimeWiring.Builder
is used to register DataFetcher
s, type resolvers, custom scalar types, and more. You can declare RuntimeWiringConfigurer
beans in your Spring config to get access to the RuntimeWiring.Builder
.
You first create type wiring for Query type by registering Query
type with RuntimeWiring.Builder
as
return new RuntimeWiringConfigurer() {
@Override
public void configure(RuntimeWiring.Builder builder) {
builder.type(
"Query",
new UnaryOperator<TypeRuntimeWiring.Builder>() {
@Override
public TypeRuntimeWiring.Builder apply(TypeRuntimeWiring.Builder builder) {
.....
}
});
}
};
TypeRuntimeWiring
As the type Query
has two fields books
and bookById
, the next step is to define DataFetcher
for fields books
and bookById
and register data fetchers with TypeRuntimeWiring.Builder
as
return new RuntimeWiringConfigurer() {
@Override
public void configure(RuntimeWiring.Builder builder) {
builder.type(
"Query",
new UnaryOperator<TypeRuntimeWiring.Builder>() {
@Override
public TypeRuntimeWiring.Builder apply(TypeRuntimeWiring.Builder builder) {
return builder
.dataFetcher(
"books",
new DataFetcher<>() {
@Override
public Collection<Book> get(DataFetchingEnvironment environment)
throws Exception {
return bookCatalogService.getBooks();
}
})
.dataFetcher(
"bookById",
new DataFetcher<>() {
@Override
public Book get(DataFetchingEnvironment environment) throws Exception {
return bookCatalogService.bookById(
Integer.parseInt(environment.getArgument("id")));
}
});
}
});
}
};
In the above code,
- Data Fetcher for field
books
fetches all books by calling the service methodbookCatalogService.getBooks()
. - Data Fetcher for field
bookById
fetches a book by calling the service methodbookCatalogService.bookById
. You can get arguments from theDataFetchingEnvironment
asenvironment.getArgument("id")
.
Similarly, for type Book
and the field ratings
, you can define DataFetcher
as:
builder.type(
"Book",
new UnaryOperator<TypeRuntimeWiring.Builder>() {
@Override
public TypeRuntimeWiring.Builder apply(TypeRuntimeWiring.Builder builder) {
return builder.dataFetcher(
"ratings",
new DataFetcher<>() {
@Override
public List<Rating> get(DataFetchingEnvironment environment)
throws Exception {
return bookCatalogService.ratings(environment.getSource());
}
});
}
});
Data Fetchers
Probably the most important concept for a GraphQL Java server is a DataFetcher
. While GraphQL Java is executing a query, it calls the appropriate DataFetcher
for each field it encounters in the query.
Every field from the schema has a
DataFetcher
associated with it. If you don’t specify anyDataFetcher
for a specific field, then the defaultPropertyDataFetcher
is used.
In above example Query.books
, Query.bookById
has a data fetcher, as does each field in type Book
. we typically don’t need specialized data fetchers on each field. As Graphql Java ships with a smart graphql.schema.PropertyDataFetcher
that knows how to follow POJO patterns based on the field name. In the example above there is a name
field and hence it will try to look for a public String getName()
POJO method to get the data.
RuntimeWiringConfigurer
You can change UnaryOperator<TypeRuntimeWiring.Builder> as a lambda expression. Then, the last step left is to tell Spring about GraphQL wiring by defining RuntimeWiringConfigurer
as bean as:
@Configuration
public class GraphQLConfiguration {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer(BookCatalogService bookCatalogService) {
return builder -> {
builder.type(
"Query",
wiring ->
wiring
.dataFetcher("books", environment -> bookCatalogService.getBooks())
.dataFetcher(
"bookById",
env -> bookCatalogService.bookById(Integer.parseInt(env.getArgument("id")))));
builder.type(
"Book",
wiring ->
wiring.dataFetcher("ratings", env -> bookCatalogService.ratings(env.getSource())));
};
}
}
Running and Testing
You can start the GraphQL service from IDE by running the main method of GraphqlJavaSpringApplication
class.
To test, open the GraphiQL editor in the browser using the link http://localhost:8080/graphiql?path=/graphql.
To enable GraphiQL editor, you must set
spring.graphql.graphiql.enabled=true
in application.properties.
Summary
GraphQL, a query language for API and a server-side runtime for executing queries, is fast emerging as an alternative to REST API. Most significantly, it solves common problems associated with REST APIs such as over-fetching and under-fetching.
Spring Boot GraphQL provides support for Spring applications built on GraphQL Java. It supports the handling of GraphQL requests over HTTP, WebSocket, and RSocket.
If you like this article, then please follow me on LinkedIn for more tips on #software architecture.
Discussion about this post