Moving Beyond REST With GraphQL
Learn about some of the basics of querying with GraphQL, as well as how to create a Spring Boot server.
Join the DZone community and get the full member experience.
Join For FreeGraphQL is a query language that matches up with Domain-Driven Design, so you can use your existing entities or models in your GraphQL design.
This query language was created by Facebook and open-sourced later in 2015, and since then, it has been maintained by the community.
To start using GraphQL, you will have to learn a new specification, because it is not a simple implementation. However, it is pretty simple if you are familiar with other query languages; it should only take you a few hours to understand in that case. Also, the GraphQL spec is well-documented and shows you how to use operations, like queries or mutations, define schemas, and/or which best practices you should follow.
How It Works
This API Query Language allows you to retrieve data from a service in one go. How can I do that? A single endpoint is exposed by GraphQL and given a schema that contains your models and operations. You can make HTTP requests to /graphql
by providing operation names, a payload, and variables.
GraphQL supports both GET and POST HTTP methods. In the case of GET, we have to use a query parameter (?query={operationName{field}}
). On the other hand, we could do a standard POST request with a JSON payload.
For example:
{
"query": "...",
"operationName": "...",
}
Remember that URLs can only sent over the Internet using the ASCII character-set.
Operations
Query
Queries are used to retrieved data, and you should use them to perform read operations in your service. Here is an example:
query {
findHotelById(id: 2) {
name
address
room {
type
}
}
}
The findHotelById
is the operation of the query, and everything else inside the operation is called payload
. We can use arguments like the one in the previous example (id: 2
) that will return the Hotel
with id 2
.
Queries also support dynamic arguments and provide a way to pass them as JSON in the operation. We will use them like this:
query MyQuery($hotelId:ID) {
findHotelById(id:$hotelId) {
name
room {
type
occupants
}
}
}
{
"hotelId": "1"
}
You could use the shorthand syntax and omit both the
query
keyword and thequery name
, but it is good practice to use these to make our code more readable, and they can be useful for debugging or identify different GraphQL requests.
If we have to create a complex query, we could use Fragments
. Fragments are reusable blocks that contain a set of fields. For example:
query MyQuery {
firstHotel: findHotelById(id:1) {
...compareHotels
}
secondHotel: findHotelById(id:3) {
...compareHotels
}
}
fragment compareHotels on Hotel {
name
room {
type
occupants
}
}
We have to use three dots followed by the fragment name to call a frament.
Mutation
Mutations are used to alter data and they should trigger insertions, updates, or deletions in your database. We can create a mutation by replacing query
with themutation
keyword. This is an example:
mutation {
newHotel(name:"test 1", address: "test 1"){
id
}
}
Just like queries, if a mutation returns an object type, you can ask for nested fields. In the previous example, the mutation creates a new Hotel
and returns the id
for the created hotel
, which was, in this case, autogenerated.
Apart from syntax, queries and mutations differ from each other on one more thing, query fields are executed in parallel, whereas mutations run sequencially.
Subscription
GraphQLsubscriptions
are a way to stream data from the server to the clients that are listening. In the same way as queries, subscriptions allow you to ask for a set of fields, but instead of making a stateless HTTP request, a WebSocket connection is used to have a stream of data coming from the server so that every time there is a change on the server, results are sent to the client, or in other words, when a client runs a mutation
the subscription is triggered.
“Executing a subscription creates a persistent function on the server that maps an underlying Source Stream to a returned Response Stream.”
/subscriptions
is by default the WebSocket subscriptions endpoint. Here’s an example of JavaScript client using subscriptions:
function subscribeToHotels() {
let socket = new WebSocket("ws://localhost:8080/subscriptions");
socket.onopen = function () {
let query = `
subscription MySubscription {
getNewHotel {
name
address
creationDate
}
}
`;
let graphqlRequest = {
query: query,
variables: {}
};
socket.send(JSON.stringify(graphqlRequest));
};
socket.onmessage = function (event) {
// handle response
}
}
Schema
Schema files are text files with .grapqhl
extension. Operations and models are defined there, and in order to do that, GraphQL provides a schema language that includes scalar types, markers, and other keywords to build complex schemas
Built-in scalar types are:
GraphQL Type | Serialized as |
---|---|
Int | Signed 32‐bit integer |
Float | Signed double-precision floating-point value |
String | UTF‐8 character sequence |
Boolean | true or false |
ID | String |
Type Markers:
GraphQL Marker | Equivalent |
---|---|
<type>! |
Not Null |
[<type>] |
List |
[<type>!] |
List of Not Null Elements |
[<type>]! |
Not Null list |
[<type>!]! |
Not Null list of Not Null Elements |
Here, you have an example:
type Hotel {
id: ID!
# Hotel name
name: String!
# Hotel address
address: String!
# Date of the hotel registry creation
creationDate: String!
# List of rooms for a particular hotel
room: [Room]!
}
We call also add comments to document your schema, and to do so, you just need to add them before each field, type or argument.
Queries
, mutations
, and subscriptions
can be created as follows:
# The Root Query for the application
type Query {
# Retrieves all hotels
findAllHotels: [Hotel]
# Retrieves a Hotel given an ID (eg: '1, 4, 12')
findHotelById(id: ID): Hotel
# Number of Hotel available
countHotels: Int
# Finds all payment methods
findAllPayments: [Payment]
}
You just need to define the name of the operation with optional parameters followed by the returned type.
Query
, Mutation
, and Subscription
keywords are used as the root of each type of operation for the application. But it is easy to add additional operations by using the extend
keyword. For example:
extend type Query {
foos: [Foo]!
}
Custom types are also extendible to avoid large list of fields.
There are also other, more advanced elements like interface
, union
, enum
, or scalar
.
How to Create a GraphQL Spring Boot Server
First, we need to:
- Define the GraphQL Schema
- Decide how the data for a query is fetched
Define GraphQL Schema
Here there is an example:
type Hotel {
id: ID!
# Hotel name
name: String!
# Hotel address
address: String!
# Date of the hotel registry creation
creationDate: String!
# List of rooms for a particular hotel
room: [Room]!
}
type Room {
id: ID!
# Room description
type: String!
# How many people can sleep in the room
occupants: Int!
}
type Payment {
id: ID!
# Payment method name
name: String!
}
# The Root Query for the application
type Query {
# Retrieves all hotels
findAllHotels: [Hotel]
# Retrieves a Hotel given an ID (eg: '1, 4, 12')
findHotelById(id: ID): Hotel
# Number of Hotel available
countHotels: Int
# Finds all payment methods
findAllPayments: [Payment]
}
# The Root Mutation for the application
type Mutation {
# Creates new hotel
newHotel(name: String!, address: String!): Hotel
# Creates new payment method
newPayment(name: String!): Payment
}
type Subscription {
getNewHotel: Hotel!
}
There is a Java GraphQL tool library that parses your schema to Java and configures classes for creating a GraphQLSchema.
By default GraphQL tools uses the location pattern
**/*.graphqls
to scan for GraphQL schemas on the classpath.
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>5.2.4</version>
</dependency>
Build the Spring Boot Application
These two libraries are required to start using GraphQL with Spring, and basically there are setting up the servlet.
<properties>
<kotlin.version>1.3.10</kotlin.version>
</properties>
<dependencies>
...
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>11.0</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>5.0.2</version>
</dependency>
</dependencies>
The first one is the GraphQL Java implementation, while the second one makes available the servlet at /graphql
.
graphl-java-tools requires kotlin.version Kotlin 1.3.10, because Spring Boot Starter parent currently overrides it with a 1.2.* version of Kotlin. Spring Boot team has indicated the Kotlin version will be upgraded to 1.3 in Spring Boot 2.2.
For this example, we will use a MongoDB repository, since we want to use Reactive Programming to get updates in real-time with GraphQL Subscription.
public interface HotelRepository extends ReactiveCrudRepository<Hotel, String> {
@Tailable
Flux<Hotel> findWithTailableCursorBy();
}
@Tailable
is required to query capped collections in MongoDB. Capped collections will keep the curson open even after reaching the end of the collection.
The next step is to create resolvers for each object defined in your schema. Query, Mutation and Subscription are root GraphQL objects, and you need to implement GraphQLQueryResolver, GraphQLMutationResolver and GraphQLSubscriptionResolver respectively so that graphql-java-tools
will be able to map the GraphQL operations with the methods created in the resolvers. Here there is an example:
@Component
@RequiredArgsConstructor
public class Query implements GraphQLQueryResolver {
private final HotelRepository hotelRepository;
private final PaymentRepository paymentRepository;
public Iterable<Hotel> findAllHotels() {
return hotelRepository.findAll().toIterable();
}
public Optional<Hotel> findHotelById(String id) {
return hotelRepository.findById(id).blockOptional();
}
public Optional<Long> countHotels() {
return hotelRepository.count().blockOptional();
}
public Iterable<Payment> findAllPayments() {
return paymentRepository.findAll().toIterable();
}
}
Method name and signature have to match with GraphQL the corresponding operation definition.
Additionally, you might need to create resolvers for nested fields. For example,
@Component
@RequiredArgsConstructor
public class HotelResolver implements GraphQLResolver<Hotel> {
private final RoomRepository roomRepository;
public Iterable<Room> getRoom(Hotel hotel) {
return roomRepository.findAllByHotelId(hotel.getId()).toIterable();
}
}
In the previous example, when we retrieve hotels we might ask for rooms as well, therefore a method to retrieve rooms by hotel ID needs to be provided.
GraphiQL, a GraphQL client
GraphiQL is a very useful tool to explore your GraphQL schema and make requests. The most simple way to start using Graphiql is to added to your pom.xml
file as a dependency.
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphiql-spring-boot-starter</artifactId>
<version>5.7.0</version>
</dependency>
By default you can hit it at /graphiql
.
Highlights and Challenges
- The main benefit of using GraphQL is that you get what you ask for in a single request, whereas with REST we tend to do “overfetching” or “underfetching.”
- GraphQL can also be simpler and faster, however, you may face unpredictable performance when multiple fields are combined.
- One of the challenges is versioning. You will have to deprecate fields and will not be able to know if a field has changed over time.
- Another point against GraphQL is caching. In GraphQL, you cannot use URLs as cache identifiers, so you need to create unique keys and implement caching in your application.
- There is also an extra overhead, since the server needs to do more processing to parse the query and verify parameters.
- Lastly, in case of a simple API the extra complexity added by GraphQL is not worth.
Conclusion
GraphQL is similar to an API gateway or proxy server that sits in front of your downstream services or data sources, and just like HTTP, we can use verbs to get exactly what we ask for. It is also an alternative to REST, SOAP or gRPC, but this does not mean you have to through away your current architecture. For instance, you could have GraphQL on top of your REST services.
This technology is becoming more mature and is available for multiple languages, including JavaScript, Python, Ruby, C#, Go, Scala or Java, and companies like Pivotal are heavily supporting GraphQL. In fact it was one of the topics presented in Spring IO 2019.
Published at DZone with permission of Sergio Martin. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments