Git Product home page Git Product logo

spring-boot-starter-graphql's Introduction

Spring Boot Starter GraphQL

The aim of this project is to get you easily started with GraphQL running in your Spring Boot based apps.

This project is based on the fine work made by the folks behind the graphql-java library.

Note: To support GraphQL's subscriptions feature this module depends on Spring 5's WebFlux module and therefore Spring Boot 2.

GraphQL Primer

In its most basic definition, GraphQL is a query language for your API.

Say you have a database of movies and you decide that you want to expose it to app developers through an API.

One way you could go about it is by using the traditional REST API approach:

GET /movies?q=big
 
[

  {
    "id":"1",
    "title":"Big",
    "synopsis":"After a wish turns 12-year-old Josh Baskin into a 30-year-old man...",
    "duration":"1h44m",
    "images":[{
       "type":"thumbnail",
       "url":"http://...",
       "resolution":"150x200"
    },
    {
       "type":"full",
       "url":"http://...",
       "resolution":"1920x1080"
    }],
    "genres":[...],
    "cast":[
        {
           "name":"Tom Hanks",
           "role":"..."
        }
    ]
  },
  
  {
    "id":"2",
    "title":"The Big Lebowski",
    "synopsis":"Jeff Bridges plays Jeff Lebowski who insists on being called "the Dude,"...",
    "duration":"1h57m",
    "images":[{
       "type":"thumbnail",
       "url":"http://...",
       "resolution":"150x200"
    },
    {
       "type":"full",
       "url":"http://...",
       "resolution":"1920x1080"
    }],
    "genres":[...],
    "cast":[
        {
           "name":"...",
           "role":"..."
        }
    ]
  }
  
  ...
  
]

While this approach is totally workable it presents several challenges:

  1. Waste. Let's say the app developers is building a screen that gives a movie listing with a thumbnail of the movie. Since the app developer does not need the movie synopsis, cast, genres etc. they are going to throw away a lot of data that had to be collected and tranferred over the wire. That's a lot of waste. One common way that people used to solve this problem is by introducing a fields query parameter to their API where you specify the fields you want to get back. This is an okay approach but comes with a significant amount of complexity to implement in the backend and does not scale very well to nested properties.

  2. Lack of standardization. REST-based APIs are extremely custom from one to the next which means that there is no standard way to interrogate them or explore them. You are at the mercy of the API author to write proper documenations and keep these up to date in order to understand how the API works.

  3. Evolution. In REST world, when you need to add new properties to your response you can either take the chance of adding them and hoping nothing breaks or you can add a /v2/movies endpoint and add your properties there. This could quickly get out of hand and adds an additional dimension to your API which you would do great without.

  4. Taxing the Backend. When you create the first version of your API it's typically nice and fast. But as you introduce more database joins, more external calls required for your response and more calculations things start slowing down. This impacts not just the new features of your apps that need the new pieces of data but any festures that use the API.

GraphQL to the rescue

Alternatively, using GraphQL we expose a single endpoint (typically /graphql) which accepts a GraphQL query from the client and responds with the requested data. Here's an example:

POST /graphql

{
   getAllMovies {
      id        // this is a field
      title,
      image(type:"thumbnail") {  // fields can be nested
         url
      }
   }
}
[

  {
    "id":"1",
    "title":"Big",
    "image:{
      "url":"http://..."
    }
  },
  
  {
    "id":"2",
    "title":"The Big Lebowski",
    "image:{
      "url":"http://..."
    }
  }
]

What just happened?

  1. Waste. Since we only want to get the movie id, title and the thumbnail URL for each movie, that's exactly what we are getting back. The most of collecting and transferring the data is completely eliminated.

  2. Lack of standardization. The GraphQL spec provides a commong ground for agreement between API authors and client consumers. This opens the door for tools such as GraphiQL and Voyager to interrogate GraphQL-based APIs.

  3. Evolution. Since GraphQL clients must explicitly request the pieces of information that they want to get back, there is no worry in adding new properties to the API and breaking older clients.

  4. Taxing the backend. Since the client is only asking for exactly what it needs, the backend it free to only perform the necessary calculations and data retrieval operations necessary to fullfill the request.

GraphQL has many more advantages and features but I hope that this gives you a good sense of what's possible with GraphQL.

For more information check out the offical GraphQL website.

Usage

Add the spring-boot-starter-graphql dependency to your Spring Boot app:

<dependency>
  <groupId>com.creactiviti</groupId>
  <artifactId>spring-boot-starter-graphql</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</dependency>

<repositories>
   <repository>
      <id>maven-snapshots</id>
      <url>http://oss.sonatype.org/content/repositories/snapshots</url>
      <layout>default</layout>
      <releases>
         <enabled>false</enabled>
      </releases>
      <snapshots>
         <enabled>true</enabled>
      </snapshots>
   </repository>
</repositories>

Kicking the tires

spring-boot-starter-graphql comes with a built-in ping query to test that everything is fine:

$ curl -s -X POST -H "Content-Type:application/json" -d '{"query":"{ ping }"}' http://localhost:8080/graphql

{
  "data": {
    "ping": "OK"
  },
  "errors": [],
  "extensions": null
}

Your first GraphQL Query

@Component
public class HelloWorld implements QueryBuilder {

  @Override
  public void build (Builder aBuilder) {
    aBuilder.field(Fields.field("hello")  // queries are just fields on the schema's built-in Query type.
                         .type(Scalars.GraphQLString)
                         .staticValue("Hi there!"));
  }

}
curl -s -X POST -H "Content-Type:application/json" -d '{"query":"{ hello }"}' http://localhost:8080/graphql

{
  "data": {
    "hello": "Hi there!"
  },
  "errors": [],
  "extensions": null
}

Your second GraphQL query

OK, let's fetch some data now to make this more interesting:

@Component
public class GetAllMoviesQuery implements QueryBuilder {

  @Override
  public void build (Builder aBuilder) {
    aBuilder.field(Fields.field("getAllMovies")
                         .type(Types.list(Movie.REF)) // query result type
                         .argument(Arguments.stringArgument("q")) // query arguments
                         .dataFetcher((env) -> {
                           
                           // in the real-world you would probably 
                           // fetch the data from a database but 
                           // i'm keeping it simple for the example. 
                           // The point is that the data can come from 
                           // anywhere: database, another service, 
                           // calculated on the fly, etc.
                           
                           Map<String, Object> movie = new HashMap<>();
                           
                           movie.put("title", "Big");
                           movie.put("synopsis", "After a wish turns 12-year-old Josh Baskin into a 30-year-old man...");
                           movie.put("duration", "1h44m");
                           
                           return Arrays.asList(movie);
                         }));
  }

}
@Component
public class Movie implements TypeBuilder {
  
  public static final String NAME = "Movie";
  public static final GraphQLTypeReference REF = Types.ref(NAME);

  @Override
  public GraphQLType build () {
    return Types.objectTypeBuilder()
                .name(NAME)
                .field(Fields.notNull(Fields.stringField("id")))
                .field(Fields.stringField("title"))
                .field(Fields.stringField("synopsis"))
                .field(Fields.stringField("duration"))
                .field(Fields.field("image")  
                             .argument(Arguments.stringArgument("type"))
                             .type(Image.REF)
                             .dataFetcher((env) -> {
                                
                                // fetch the right image.
                               
                               // will NOT be executed if the user
                               // did not ask for the image field!
                                 
                             })
                )
                .build();
  }

}
$ curl -s -X POST -H "Content-Type:application/json" -d '{"query":"{ getAllMovies { title synopsis } }"}' http://localhost:8080/graphql

{
  "data": {
    "getAllMovies": [
      {
        "title": "Big",
        "synopsis": "After a wish turns 12-year-old Josh Baskin into a 30-year-old man..."
      }
    ]
  },
  "errors": [],
  "extensions": null
}

Mutations

So far we looked at reading data from the API. Mutations are GraphQL's approach to writing data.

@Component
public class AddMovie implements MutationBuilder {

  @Override
  public void build (Builder aBuilder) {
    aBuilder.field(Fields.field("addMovie")
                         .argument(Arguments.notNull(Arguments.stringArgument("title")))
                         .argument(Arguments.stringArgument("synopsis"))
                         .argument(Arguments.stringArgument("duration"))
                         .type(Movie.REF)
                         .dataFetcher((env) -> {
                           Map<String,Object> record = new HashMap<>();
                           
                           record.put("id", UUID.randomUUID().toString().replace("-",""));
                           record.put("title", env.getArgument("title"));
                           record.put("synopsis", env.getArgument("synopsis"));
                           record.put("duration", env.getArgument("duration"));
                           
                           // save the the database
                           
                           return record;
                         }));
  }

}
$ curl -s -X POST -H "Content-Type:application/json" -d '{"query":"mutation { addMovie (title:\"Big Fish\") { id title } }"}' http://localhost:8080/graphql

{
  "data": {
    "addMovie": {
      "id": "8294094ebb034c7f82ef1170b95f69b0",
      "title": "Big Fish"
    }
  },
  "errors": [],
  "extensions": null
}

Subscriptions

Both queries and mutations make use of the traditional request/response model: the client makes a request with the specifics of what it wants to get back and the server responds appropriately.

Subscriptions on the other hand, are publish/subsribe based mechanism: the server published some sort of notifications that clients can subscribe to.

For example we can let clients know whenever a movie was added:

@Component
public class MovieAddedSubscription implements SubscriptionBuilder {
  
  private static final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
  
  @Override
  public void build (Builder aBuilder) {
    aBuilder.field(Fields.field("movieAdded")
                         .type(Movie.REF)
                         .dataFetcher((env)->Flux.create ((emitter) -> {
                           
                           // use a scheduled executor to simulate an 
                           // event happening on another thread event 
                           // second.
                           
                           executor.scheduleAtFixedRate(() -> onMovieAdded(emitter), 1, 1, TimeUnit.SECONDS);
                           
                         })));
  }
  
  private void onMovieAdded (FluxSink<Object> aEmitter) {
    Map<String, String> movie = new HashMap<>();
    movie.put("id", UUID.randomUUID().toString().replace("-",""));
    movie.put("title", "...");
    aEmitter.next(movie);
  }

}
$ curl -s -X POST -H "Content-Type:application/json" -H "Accept:text/event-stream" -d '{"query":"subscription { movieAdded { id title } }"}' http://localhost:8080/graphql

data:{"data":{"id":"ef006c125e61435d965bbd9492d00990","title":"..."},"errors":[],"extensions":null}

data:{"data":{"id":"160ac99f81f740589b6062f5cb70915c","title":"..."},"errors":[],"extensions":null}

... and on every second

Note: Make sure to use the Accept:text/event-stream when subscribing.

License

Version 2.0 of the Apache License.

spring-boot-starter-graphql's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spring-boot-starter-graphql's Issues

More Examples

I'm attempting to hook up a subscription to reactive MongoDB repository. However, no events are ever emitted to GraphiQL. It is a tailable flux (capped collection created in MongoDB). More examples would be helpful to implement the library, especially subscriptions.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.