Over the span of the past few months I’ve been learning about GraphQL and how to use it as an alternative to RESTful API development. The focus thus far had been around GraphQL and Golang as well as GraphQL and Node.js. I might have squeeze out all I can on those two development languages so I’ve decided to switch gears and press my luck with some Java.
In this tutorial, we’re going to see how to quickly get up and running with GraphQL using Java and the popular Spring Boot framework.
Before we get into things, I want to give credit where credit is deserved. Prithviraj Pawar wrote a great article titled, How to get Your GraphQL Java Server Up and Running in no Time, which gave me a lot of ideas even though there were things to be desired, such as a database instead of mock data. I encourage you to read his article after reading through mine.
The Requirements
There are a few prerequisites that must be met before we jump into the GraphQL and development side of things.
We’re going to assume that you already have Couchbase installed, configured, and running. Anything after Couchbase Server 5.0 will do because we’re only going to be using basic features.
We’re also going to assume that you’ve downloaded a Spring Boot base project. The Spring Boot website has a great generator that lets you define the dependencies. If you want to stay consistent to my guide, you’ll need to create a Gradle project rather than Maven.
Defining the Gradle Project Dependencies
When you have a Spring Boot template in hand, it will likely be very bare. We need to include the dependencies we plan to use in our Gradle configuration.
Open the project’s build.gradle file and include the following:
|
1 2 3 4 5 6 |
dependencies { compile(‘org.springframework.boot:spring-boot-starter-web’) compile(‘com.couchbase.client:java-client’) compile(‘com.graphql-java:graphql-java:9.0’) testCompile(‘org.springframework.boot:spring-boot-starter-test’) } |
We’ll be using Spring Boot to serve our GraphQL endpoint, the Couchbase Java SDK for interactions with our database, and the GraphQL plugin for our query processing.
Bootstrapping the Java API with RESTful Endpoints
With the dependencies in place, we can start developing our API. Even though we’re using GraphQL as a replacement to all the common RESTful API stuff, it doesn’t mean that REST is completely out of the equation. We still need an endpoint to send our GraphQL queries to.
Depending on what you named your project, package, etc., open the file that contains your main function and include the following:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
paquete com.Couchbase.GraphQL; importar com.Couchbase.client.Java.Cubo; importar com.Couchbase.client.Java.Clúster; importar com.Couchbase.client.Java.CouchbaseCluster; importar com.Couchbase.client.Java.documento.json.ObjetoJson; importar GraphQL.schema.DataFetcher; importar org.Spring Framework.beans.factory.anotación.Autowired; importar org.Spring Framework.beans.factory.anotación.Value; importar org.Spring Framework.boot.SpringApplication; importar org.Spring Framework.boot.autoconfigure.SpringBootApplication; importar org.Spring Framework.context.anotación.Bean; importar org.Spring Framework.http.HttpStatus; importar org.Spring Framework.http.ResponseEntity; importar org.Spring Framework.web.bind.anotación.RequestBody; importar org.Spring Framework.web.bind.anotación.RequestMapping; importar org.Spring Framework.web.bind.anotación.RequestMethod; importar org.Spring Framework.web.bind.anotación.RestController; importar GraphQL.ExecutionInput; importar GraphQL.ExecutionResult; importar GraphQL.GraphQL; importar GraphQL.schema.GraphQLSchema; importar GraphQL.schema.StaticDataFetcher; importar GraphQL.schema.idl.RuntimeWiring; importar GraphQL.schema.idl.SchemaGenerator; importar GraphQL.schema.idl.SchemaParser; importar GraphQL.schema.idl.TypeDefinitionRegistry; importar javax.anotación.PostConstruct; importar estático GraphQL.schema.idl.RuntimeWiring.newRuntimeWiring; importar Java.yo.File; importar Java.nio.file.Files; importar Java.nio.file.Paths; importar Java.útil.HashMap; importar Java.útil.Mapa; @SpringBootApplication @RestController público clase GraphqlApplication { público estático vacío main(Cadena[] args) { SpringApplication.run(GraphqlApplication.clase, args); } @RequestMapping(valor=“/”, method= RequestMethod.GET) público Objeto rootEndpoint() { Mapa<Cadena, ObjetoEl resultado de tu pregunta se muestra a continuación: respuesta = nuevo HashMap<Cadena, ObjetoEl resultado de tu pregunta se muestra a continuación:(); respuesta.poner(“message”, “Hello World”); regresar respuesta; } @RequestMapping(valor=“/graphql”, method= RequestMethod.POST) público Objeto GraphQL(@RequestBody Cadena request) { ObjetoJson jsonRequest = ObjetoJson.fromJson(request); regresar jsonRequest; } } |
Remember, your package and class name may not match mine. Fill in the gaps where appropriate.
You’ll notice that we have two endpoints, where one is completely optional. I created the rootEndpoint as a way to test that my RESTful API was in fact working. The GraphQL endpoint is where we’re going to be spending our time. It expects a POST request as well as a request body. The request body will eventually be an actual GraphQL query, but for now we can leave it as is.
Designing a GraphQL Schema Document
Up until now we’ve only done some basic Spring Boot preparation work and nothing really related to GraphQL. For GraphQL to be possible, we need to know a defined schema that explains the queries that are available as well as the types of data that we are working with. There are numerous ways to do this, but we’re going to focus on a schema document.
Within the resources directory of your project, include a schema.graphql file that contains the following:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
tipo Consulta{ pokemons: [Pokemon] games: [Game] game(identificación: Cadena): Game } tipo Game { identificación: Cadena nombre: Cadena } tipo Pokemon { identificación: Cadena game: Game nombre: Cadena height: Int weight: Int } |
In case it isn’t obvious, we’re going to be working with Pokemon data. In the above file we’ve defined the queries that are available and the two data types that are available.
There are a variety of Pokemon games on the market and there are different Pokemon in each of the games, hence the relationship that we’ve defined. If you’re a true Pokemon fan you’ll know there is a little bit more to it than this, but for this example it is enough.
Developing the Fetching Logic for the Couchbase NoSQL Database
With the schema defined, we need to come up with database logic to be ran when a query is executed and that database logic must return results that match each of the two data types.
To make our lives a bit easier when it comes to project maintenance, we’re going to create a separate file for managing the database. Within your package, create a Database.java file that contains the following:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
paquete com.Couchbase.GraphQL; importar com.Couchbase.client.Java.Cubo; importar com.Couchbase.client.Java.documento.JsonDocument; importar com.Couchbase.client.Java.documento.json.ObjetoJson; importar com.Couchbase.client.Java.consulta.N1qlQuery; importar com.Couchbase.client.Java.consulta.N1qlQueryResult; importar com.Couchbase.client.Java.consulta.N1qlQueryRow; importar GraphQL.schema.DataFetcher; importar Java.útil.ArrayList; importar Java.útil.HashMap; importar Java.útil.Lista; importar Java.útil.Mapa; público clase Database { público estático DataFetcher getPokemonData(Cubo cubeta) { } público estático DataFetcher getGameData(Cubo cubeta) { } público estático DataFetcher getGamesData(Cubo cubeta) { } privado estático Lista<Mapa<Cadena, Objeto>> extractResultOrThrow(N1qlQueryResult resultado) { } } |
For the most part, the above set of functions should look familiar because we’ve defined them somewhat to match what we had in our GraphQL schema file.
A few things to note before we start filling each of the functions:
- The bucket will be defined in our main project file shortly after we define our logic.
- GraphQL expects a certain type of response, hence the
extractResultOrThrowwrapper function that we’ve created.
With that noted, let’s start by creating the wrapper function. The extractResultOrThrow function will be used only on N1QL queries and it looks like the following:
|
1 2 3 4 5 6 7 |
privado estático Lista<Mapa<Cadena, Objeto>> extractResultOrThrow(N1qlQueryResult resultado) { Lista<Mapa<Cadena, Objeto>> contenido = nuevo ArrayList<Mapa<Cadena, Objeto>>(); para (N1qlQueryRow fila : resultado) { contenido.añadir(fila.valor().toMap()); } regresar contenido; } |
Essentially we’re just looping through a N1QL result set and creating a Lista of Mapa from it.
Assuming that we have game data in our database, we can query for that data in the getGamesData function:
|
1 2 3 4 5 6 7 8 9 10 |
público estático DataFetcher getGamesData(Cubo cubeta) { regresar environment -> { Sistema.out.println(“FETCHING GAMES DATA…”); Cadena statement = “SELECT example.* “ + “FROM example “ + “WHERE type = ‘game'”; N1qlQueryResult queryResult = cubeta.consulta(N1qlQuery.simple(statement)); regresar extractResultOrThrow(queryResult); }; } |
Using N1QL we can query for documents that match the tipo of game and process the results with our wrapper function. If you really wanted to be strict, instead of using a wildcard asterisk, you could define the properties that match your schema.
The next function, getPokemonData, will allow us to query for our Pokemon data in a similar fashion to how we did it with game data.
|
1 2 3 4 5 6 7 8 9 10 |
público estático DataFetcher getPokemonData(Cubo cubeta) { regresar environment -> { Sistema.out.println(“FETCHING POKEMON DATA…”); Cadena statement = “SELECT example.* “ + “FROM example “ + “WHERE type = ‘pokemon'”; N1qlQueryResult queryResult = cubeta.consulta(N1qlQuery.simple(statement)); regresar extractResultOrThrow(queryResult); }; } |
You’ll notice that we didn’t do anything new beyond change the tipo property. This is where things can get interesting. We have a data relationship for Pokemon in regards to game data. We could either fetch it in our current query or break it up to be more modular and potentially more light weight. We’re going to go with the modular and light weight option.
This brings us to our getGameData function:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
público estático DataFetcher getGameData(Cubo cubeta) { regresar environment -> { HashMap<Cadena, ObjetoEl resultado de tu pregunta se muestra a continuación: parent = environment.getSource(); JsonDocument documento; si(parent != nulo) { Sistema.out.println(“FETCHING GAME DATA FOR “ + (Cadena)parent.conseguir(“game”) + “…”); documento = cubeta.conseguir((Cadena) parent.conseguir(“game”)); } otro { Sistema.out.println(“FETCHING GAME DATA FOR “ + (Cadena)environment.getArgument(“id”) + “…”); documento = cubeta.conseguir((Cadena)environment.getArgument(“id”)); } regresar documento.contenido().toMap(); }; } |
There are two things happening in the above function, neither of which use N1QL.
In our schema we have defined an argument based query for game data as well as a data relationship in the Pokemon data. We’re going to accommodate both those scenarios in the same function.
Using the environment.getSource() function we can see if there is any parent data that goes with the request. An example of this parent data might be when we query for Pokemon data. The game field refers to the getGameData function while the other data such as the name, weight, or other, refers to the parent data. In the database, the game information is stored as an id so after we extract it from the parent data, we can do a lookup for game data.
The other scenario is if we pass an argument that represents a game id.
Both are valid and both do different things. In the end, we only want a single result rather than an array of results.
Wiring the Application Together for Successful GraphQL Processing
We are in the home stretch now. We’ve got our API and our database logic in place. The final step is to wire the GraphQL schema to the database logic and watch the results in all their glory.
Remember in the previous step I mentioned configuring our bucket information? Let’s take care of that first. Open the project’s application.properties file found in the resources directory. Include the following:
|
1 2 3 4 |
hostname=127.0.0.1 cubeta=example nombre de usuario=example contraseña=123456 |
My database information is just an example. Make sure to replace it with the information that you’re using for your instance of Couchbase.
Back in your main project file, we can map the values and connect to our instance:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
paquete com.Couchbase.GraphQL; importar com.Couchbase.client.Java.Cubo; importar com.Couchbase.client.Java.Clúster; importar com.Couchbase.client.Java.CouchbaseCluster; importar com.Couchbase.client.Java.documento.json.ObjetoJson; importar GraphQL.schema.DataFetcher; importar org.Spring Framework.beans.factory.anotación.Autowired; importar org.Spring Framework.beans.factory.anotación.Value; importar org.Spring Framework.boot.SpringApplication; importar org.Spring Framework.boot.autoconfigure.SpringBootApplication; importar org.Spring Framework.context.anotación.Bean; importar org.Spring Framework.http.HttpStatus; importar org.Spring Framework.http.ResponseEntity; importar org.Spring Framework.web.bind.anotación.RequestBody; importar org.Spring Framework.web.bind.anotación.RequestMapping; importar org.Spring Framework.web.bind.anotación.RequestMethod; importar org.Spring Framework.web.bind.anotación.RestController; importar GraphQL.ExecutionInput; importar GraphQL.ExecutionResult; importar GraphQL.GraphQL; importar GraphQL.schema.GraphQLSchema; importar GraphQL.schema.StaticDataFetcher; importar GraphQL.schema.idl.RuntimeWiring; importar GraphQL.schema.idl.SchemaGenerator; importar GraphQL.schema.idl.SchemaParser; importar GraphQL.schema.idl.TypeDefinitionRegistry; importar javax.anotación.PostConstruct; importar estático GraphQL.schema.idl.RuntimeWiring.newRuntimeWiring; importar Java.yo.File; importar Java.nio.file.Files; importar Java.nio.file.Paths; importar Java.útil.HashMap; importar Java.útil.Mapa; @SpringBootApplication @RestController público clase GraphqlApplication { @Value(“${hostname}”) privado Cadena hostname; @Value(“${bucket}”) privado Cadena cubeta; @Value(“${username}”) privado Cadena nombre de usuario; @Value(“${password}”) privado Cadena contraseña; público @Bean Clúster clúster() { Clúster clúster = CouchbaseCluster.crear(este.hostname); clúster.authenticate(este.nombre de usuario, este.contraseña); regresar clúster; } público @Bean Cubo cubeta() { regresar clúster().openBucket(este.cubeta); } privado GraphQL build; público estático vacío main(Cadena[] args) { SpringApplication.run(GraphqlApplication.clase, args); } @RequestMapping(valor=“/”, method= RequestMethod.GET) público Objeto rootEndpoint() { } @RequestMapping(valor=“/graphql”, method= RequestMethod.POST) público Objeto GraphQL(@RequestBody Cadena request) { } } |
Notice that we’ve created two Bean and used the information from our properties file. Just like that we’re connected to Couchbase.
The next step is to initialize our GraphQL schema. There are numerous ways to do this, but I found it easier using the Spring Boot annotation for when the application starts:
0
El init function will be called when the application starts. It will load our schema file from the resources directory and define wiring. For example, we know that pokemons, games, y game(id: String) are all types of queries. Each get mapped to the appropriate database function and the open bucket is passed. Then we establish a mapping of game to the Pokemon type. We are more or less defining how fields and queries are tied to database interaction.
Once we build the schema we can tie it to our class variable. With the built schema, we can finalize our GraphQL RESTful endpoint:
1
In our endpoint we take the query string that was passed with the request and execute it with our built schema. The result will be whatever the client has asked for in the query.
So what does a query look like? Take the following for example:
2
The above query is probably not something anyone would ever need to run, but you could if you wanted to. In the above example we are querying for all Pokemon data and the game they are a part of. We are also querying for all games as well as a specific game. All three parts of the query are unrelated, but exist in the same query.
As a cool fun fact, in the pokemons query, if we choose not to query for game data, the database function is never called. GraphQL will only execute functions as they are needed.
Conclusión
You just saw how to build a GraphQL application using Spring Boot, Java, and Couchbase Server as the NoSQL database. As you saw in my sample query, we were able to query many different unrelated pieces of data in a single request, something that would have required several in a RESTful API.
For more information on using the Java SDK with Couchbase, check out the Couchbase Developer Portal.

Deja un comentario
Lo siento, debes estar conectado para publicar un comentario.