{"id":13770321,"url":"https://github.com/runabol/spring-boot-starter-graphql","last_synced_at":"2025-06-20T15:12:02.411Z","repository":{"id":94886476,"uuid":"120852192","full_name":"runabol/spring-boot-starter-graphql","owner":"runabol","description":"Adding GraphQL goodness to Spring Boot apps","archived":false,"fork":false,"pushed_at":"2018-02-11T16:20:57.000Z","size":84,"stargazers_count":39,"open_issues_count":1,"forks_count":10,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-05-07T18:07:56.040Z","etag":null,"topics":["apache2","graphql","java","spring","spring-boot","spring-boot-starter"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/runabol.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2018-02-09T03:36:10.000Z","updated_at":"2024-03-31T14:20:37.000Z","dependencies_parsed_at":null,"dependency_job_id":"fad0fbe0-ad70-432e-a506-268d37761e7c","html_url":"https://github.com/runabol/spring-boot-starter-graphql","commit_stats":null,"previous_names":["runabol/spring-boot-starter-graphql"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/runabol/spring-boot-starter-graphql","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runabol%2Fspring-boot-starter-graphql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runabol%2Fspring-boot-starter-graphql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runabol%2Fspring-boot-starter-graphql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runabol%2Fspring-boot-starter-graphql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/runabol","download_url":"https://codeload.github.com/runabol/spring-boot-starter-graphql/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runabol%2Fspring-boot-starter-graphql/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259747196,"owners_count":22905308,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apache2","graphql","java","spring","spring-boot","spring-boot-starter"],"created_at":"2024-08-03T17:00:36.216Z","updated_at":"2025-06-20T15:11:57.398Z","avatar_url":"https://github.com/runabol.png","language":"Java","readme":"# Spring Boot Starter GraphQL\n\nThe aim of this project is to get you easily started with GraphQL running in your Spring Boot based apps.\n\nThis project is based on the fine work made by the folks behind the [graphql-java](https://github.com/graphql-java/graphql-java) library.\n\n**Note**: To support GraphQL's [subscriptions](http://graphql.org/blog/subscriptions-in-graphql-and-relay/) feature this module depends on Spring 5's WebFlux module and therefore Spring Boot 2.\n\n# GraphQL Primer\n\nIn its most basic definition, GraphQL is a **query language** for your API.\n\nSay you have a database of movies and you decide that you want to expose it to app developers through an API. \n\nOne way you could go about it is by using the traditional REST API approach:\n\n```\nGET /movies?q=big\n \n[\n\n  {\n    \"id\":\"1\",\n    \"title\":\"Big\",\n    \"synopsis\":\"After a wish turns 12-year-old Josh Baskin into a 30-year-old man...\",\n    \"duration\":\"1h44m\",\n    \"images\":[{\n       \"type\":\"thumbnail\",\n       \"url\":\"http://...\",\n       \"resolution\":\"150x200\"\n    },\n    {\n       \"type\":\"full\",\n       \"url\":\"http://...\",\n       \"resolution\":\"1920x1080\"\n    }],\n    \"genres\":[...],\n    \"cast\":[\n        {\n           \"name\":\"Tom Hanks\",\n           \"role\":\"...\"\n        }\n    ]\n  },\n  \n  {\n    \"id\":\"2\",\n    \"title\":\"The Big Lebowski\",\n    \"synopsis\":\"Jeff Bridges plays Jeff Lebowski who insists on being called \"the Dude,\"...\",\n    \"duration\":\"1h57m\",\n    \"images\":[{\n       \"type\":\"thumbnail\",\n       \"url\":\"http://...\",\n       \"resolution\":\"150x200\"\n    },\n    {\n       \"type\":\"full\",\n       \"url\":\"http://...\",\n       \"resolution\":\"1920x1080\"\n    }],\n    \"genres\":[...],\n    \"cast\":[\n        {\n           \"name\":\"...\",\n           \"role\":\"...\"\n        }\n    ]\n  }\n  \n  ...\n  \n]\n``` \n\nWhile this approach is totally workable it presents several challenges: \n\n1. **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.\n\n2. **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. \n\n3. **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.\n  \n4. **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.\n\n## GraphQL to the rescue  \n\nAlternatively, 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: \n\n```\nPOST /graphql\n\n{\n   getAllMovies {\n      id        // this is a field\n      title,\n      image(type:\"thumbnail\") {  // fields can be nested\n         url\n      }\n   }\n}\n```\n\n```\n[\n\n  {\n    \"id\":\"1\",\n    \"title\":\"Big\",\n    \"image:{\n      \"url\":\"http://...\"\n    }\n  },\n  \n  {\n    \"id\":\"2\",\n    \"title\":\"The Big Lebowski\",\n    \"image:{\n      \"url\":\"http://...\"\n    }\n  }\n]\n```\n\nWhat just happened? \n\n1. **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.\n\n2. **Lack of standardization**. The GraphQL [spec](http://facebook.github.io/graphql/October2016/) provides a commong ground for agreement between API authors and client consumers. This opens the door for tools such as [GraphiQL](https://github.com/graphql/graphiql) and [Voyager](https://github.com/APIs-guru/graphql-voyager) to interrogate GraphQL-based APIs.\n\n3. **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. \n\n4. **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. \n\nGraphQL has many more advantages and features but I hope that this gives you a good sense of what's possible with GraphQL.\n\nFor more information check out the offical [GraphQL](http://graphql.org/) website.\n\n# Usage\n\nAdd the `spring-boot-starter-graphql` dependency to your Spring Boot app:\n\n```\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.creactiviti\u003c/groupId\u003e\n  \u003cartifactId\u003espring-boot-starter-graphql\u003c/artifactId\u003e\n  \u003cversion\u003e0.0.1-SNAPSHOT\u003c/version\u003e\n\u003c/dependency\u003e\n\n\u003crepositories\u003e\n   \u003crepository\u003e\n      \u003cid\u003emaven-snapshots\u003c/id\u003e\n      \u003curl\u003ehttp://oss.sonatype.org/content/repositories/snapshots\u003c/url\u003e\n      \u003clayout\u003edefault\u003c/layout\u003e\n      \u003creleases\u003e\n         \u003cenabled\u003efalse\u003c/enabled\u003e\n      \u003c/releases\u003e\n      \u003csnapshots\u003e\n         \u003cenabled\u003etrue\u003c/enabled\u003e\n      \u003c/snapshots\u003e\n   \u003c/repository\u003e\n\u003c/repositories\u003e\n```\n\n# Kicking the tires\n\n`spring-boot-starter-graphql` comes with a built-in `ping` query to test that everything is fine:\n\n``` \n$ curl -s -X POST -H \"Content-Type:application/json\" -d '{\"query\":\"{ ping }\"}' http://localhost:8080/graphql\n\n{\n  \"data\": {\n    \"ping\": \"OK\"\n  },\n  \"errors\": [],\n  \"extensions\": null\n}\n```\n\n# Your first GraphQL Query\n\n```\n@Component\npublic class HelloWorld implements QueryBuilder {\n\n  @Override\n  public void build (Builder aBuilder) {\n    aBuilder.field(Fields.field(\"hello\")  // queries are just fields on the schema's built-in Query type.\n                         .type(Scalars.GraphQLString)\n                         .staticValue(\"Hi there!\"));\n  }\n\n}\n```\n\n```\ncurl -s -X POST -H \"Content-Type:application/json\" -d '{\"query\":\"{ hello }\"}' http://localhost:8080/graphql\n\n{\n  \"data\": {\n    \"hello\": \"Hi there!\"\n  },\n  \"errors\": [],\n  \"extensions\": null\n}\n```\n\n# Your second GraphQL query\n\nOK, let's fetch some data now to make this more interesting:\n\n```\n@Component\npublic class GetAllMoviesQuery implements QueryBuilder {\n\n  @Override\n  public void build (Builder aBuilder) {\n    aBuilder.field(Fields.field(\"getAllMovies\")\n                         .type(Types.list(Movie.REF)) // query result type\n                         .argument(Arguments.stringArgument(\"q\")) // query arguments\n                         .dataFetcher((env) -\u003e {\n                           \n                           // in the real-world you would probably \n                           // fetch the data from a database but \n                           // i'm keeping it simple for the example. \n                           // The point is that the data can come from \n                           // anywhere: database, another service, \n                           // calculated on the fly, etc.\n                           \n                           Map\u003cString, Object\u003e movie = new HashMap\u003c\u003e();\n                           \n                           movie.put(\"title\", \"Big\");\n                           movie.put(\"synopsis\", \"After a wish turns 12-year-old Josh Baskin into a 30-year-old man...\");\n                           movie.put(\"duration\", \"1h44m\");\n                           \n                           return Arrays.asList(movie);\n                         }));\n  }\n\n}\n```\n\n```\n@Component\npublic class Movie implements TypeBuilder {\n  \n  public static final String NAME = \"Movie\";\n  public static final GraphQLTypeReference REF = Types.ref(NAME);\n\n  @Override\n  public GraphQLType build () {\n    return Types.objectTypeBuilder()\n                .name(NAME)\n                .field(Fields.notNull(Fields.stringField(\"id\")))\n                .field(Fields.stringField(\"title\"))\n                .field(Fields.stringField(\"synopsis\"))\n                .field(Fields.stringField(\"duration\"))\n                .field(Fields.field(\"image\")  \n                             .argument(Arguments.stringArgument(\"type\"))\n                             .type(Image.REF)\n                             .dataFetcher((env) -\u003e {\n                                \n                                // fetch the right image.\n                               \n                               // will NOT be executed if the user\n                               // did not ask for the image field!\n                                 \n                             })\n                )\n                .build();\n  }\n\n}\n```\n\n```\n$ curl -s -X POST -H \"Content-Type:application/json\" -d '{\"query\":\"{ getAllMovies { title synopsis } }\"}' http://localhost:8080/graphql\n\n{\n  \"data\": {\n    \"getAllMovies\": [\n      {\n        \"title\": \"Big\",\n        \"synopsis\": \"After a wish turns 12-year-old Josh Baskin into a 30-year-old man...\"\n      }\n    ]\n  },\n  \"errors\": [],\n  \"extensions\": null\n}\n```\n\n# Mutations\n\nSo far we looked at reading data from the API. Mutations are GraphQL's approach to writing data.\n\n```\n@Component\npublic class AddMovie implements MutationBuilder {\n\n  @Override\n  public void build (Builder aBuilder) {\n    aBuilder.field(Fields.field(\"addMovie\")\n                         .argument(Arguments.notNull(Arguments.stringArgument(\"title\")))\n                         .argument(Arguments.stringArgument(\"synopsis\"))\n                         .argument(Arguments.stringArgument(\"duration\"))\n                         .type(Movie.REF)\n                         .dataFetcher((env) -\u003e {\n                           Map\u003cString,Object\u003e record = new HashMap\u003c\u003e();\n                           \n                           record.put(\"id\", UUID.randomUUID().toString().replace(\"-\",\"\"));\n                           record.put(\"title\", env.getArgument(\"title\"));\n                           record.put(\"synopsis\", env.getArgument(\"synopsis\"));\n                           record.put(\"duration\", env.getArgument(\"duration\"));\n                           \n                           // save the the database\n                           \n                           return record;\n                         }));\n  }\n\n}\n```\n\n```\n$ curl -s -X POST -H \"Content-Type:application/json\" -d '{\"query\":\"mutation { addMovie (title:\\\"Big Fish\\\") { id title } }\"}' http://localhost:8080/graphql\n\n{\n  \"data\": {\n    \"addMovie\": {\n      \"id\": \"8294094ebb034c7f82ef1170b95f69b0\",\n      \"title\": \"Big Fish\"\n    }\n  },\n  \"errors\": [],\n  \"extensions\": null\n}\n```\n\n\n\n# Subscriptions\n\nBoth 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. \n\nSubscriptions on the other hand, are publish/subsribe based mechanism: the server published some sort of notifications that clients can subscribe to. \n\nFor example we can let clients know whenever a movie was added:\n\n```\n@Component\npublic class MovieAddedSubscription implements SubscriptionBuilder {\n  \n  private static final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();\n  \n  @Override\n  public void build (Builder aBuilder) {\n    aBuilder.field(Fields.field(\"movieAdded\")\n                         .type(Movie.REF)\n                         .dataFetcher((env)-\u003eFlux.create ((emitter) -\u003e {\n                           \n                           // use a scheduled executor to simulate an \n                           // event happening on another thread event \n                           // second.\n                           \n                           executor.scheduleAtFixedRate(() -\u003e onMovieAdded(emitter), 1, 1, TimeUnit.SECONDS);\n                           \n                         })));\n  }\n  \n  private void onMovieAdded (FluxSink\u003cObject\u003e aEmitter) {\n    Map\u003cString, String\u003e movie = new HashMap\u003c\u003e();\n    movie.put(\"id\", UUID.randomUUID().toString().replace(\"-\",\"\"));\n    movie.put(\"title\", \"...\");\n    aEmitter.next(movie);\n  }\n\n}\n```\n\n```\n$ curl -s -X POST -H \"Content-Type:application/json\" -H \"Accept:text/event-stream\" -d '{\"query\":\"subscription { movieAdded { id title } }\"}' http://localhost:8080/graphql\n\ndata:{\"data\":{\"id\":\"ef006c125e61435d965bbd9492d00990\",\"title\":\"...\"},\"errors\":[],\"extensions\":null}\n\ndata:{\"data\":{\"id\":\"160ac99f81f740589b6062f5cb70915c\",\"title\":\"...\"},\"errors\":[],\"extensions\":null}\n\n... and on every second\n\n```  \n\n**Note:** Make sure to use the `Accept:text/event-stream` when subscribing.\n\n# License\n\nVersion 2.0 of the Apache License.","funding_links":[],"categories":["Exposing a Schema"],"sub_categories":["Code First"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frunabol%2Fspring-boot-starter-graphql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frunabol%2Fspring-boot-starter-graphql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frunabol%2Fspring-boot-starter-graphql/lists"}