Skip to content

Instantly share code, notes, and snippets.

@marceloverdijk
Last active January 8, 2020 20:42
Show Gist options
  • Save marceloverdijk/ba4ee3e1a8440b75ae57c989ee8b4895 to your computer and use it in GitHub Desktop.
Save marceloverdijk/ba4ee3e1a8440b75ae57c989ee8b4895 to your computer and use it in GitHub Desktop.
Quarkus GraphQL endpoint using RESTEasy-Jackson
//
// build.gradle
//
implementation "com.graphql-java:graphql-java:13.0"
implementation "io.quarkus:quarkus-resteasy"
implementation "io.quarkus:quarkus-resteasy-jackson"
//
// GraphQLEndoint.java
//
package com.example.graphql.endpoint;
import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.ExecutionInput;
import graphql.GraphQL;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
* The GraphQL controller.
*
* @author Marcel Overdijk
*/
@Path("/graphql")
@Produces(APPLICATION_JSON)
public class GraphQLEndpoint {
private static final MediaType APPLICATION_GRAPHQL_TYPE = new MediaType("application", "graphql");
private final GraphQL graphQL;
private final ObjectMapper objectMapper;
public GraphQLEndpoint(GraphQL graphQL, ObjectMapper objectMapper) {
this.graphQL = graphQL;
this.objectMapper = objectMapper;
}
@GET
public Response get(
@QueryParam("query") String query,
@QueryParam("operationName") String operationName,
@QueryParam("variables") String variables) {
// https://graphql.org/learn/serving-over-http/#get-request
//
// When receiving an HTTP GET request, the GraphQL query should be specified in the "query" query string.
// For example, if we wanted to execute the following GraphQL query:
//
// {
// me {
// name
// }
// }
//
// This request could be sent via an HTTP GET like so:
//
// http://myapi/graphql?query={me{name}}
//
// Query variables can be sent as a JSON-encoded string in an additional query parameter called "variables".
// If the query contains several named operations,
// an "operationName" query parameter can be used to control which one should be executed.
return executeRequest(query, operationName, jsonToMap(variables));
}
@POST
public Response post(
@HeaderParam(CONTENT_TYPE) String contentType,
@QueryParam("query") String query,
@QueryParam("operationName") String operationName,
@QueryParam("variables") String variables,
String body) {
MediaType mediaType = null;
try {
mediaType = MediaType.valueOf(contentType);
} catch (IllegalArgumentException ignore) {
}
// https://graphql.org/learn/serving-over-http/#post-request
//
// A standard GraphQL POST request should use the application/json content type,
// and include a JSON-encoded body of the following form:
//
// {
// "query": "...",
// "operationName": "...",
// "variables": { "myVariable": "someValue", ... }
// }
if (APPLICATION_JSON_TYPE.isCompatible(mediaType)) {
var request = jsonToObject(body, GraphQLRequest.class);
return executeRequest(request.getQuery(), request.getOperationName(), request.getVariables());
}
// In addition to the above, we recommend supporting two additional cases:
//
// * If the "query" query string parameter is present (as in the GET example above),
// it should be parsed and handled in the same way as the HTTP GET case.
if (query != null) {
return executeRequest(query, operationName, jsonToMap(variables));
}
// * If the "application/graphql" Content-Type header is present,
// treat the HTTP POST body contents as the GraphQL query string.
if (APPLICATION_GRAPHQL_TYPE.isCompatible(mediaType)) {
return executeRequest(body, null, null);
}
return Response.status(BAD_REQUEST).build();
}
private Response executeRequest(String query, String operationName, Map<String, Object> variables) {
var executionInput = ExecutionInput.newExecutionInput()
.query(query != null ? query : "")
.operationName(operationName)
.variables(variables)
.build();
var executionResult = graphQL.execute(executionInput);
try {
String json = objectMapper.writeValueAsString(executionResult.toSpecification());
return Response.ok(json).build();
} catch (IOException e) {
throw new RuntimeException("Could not convert object to JSON: " + e.getMessage(), e);
}
}
private Map<String, Object> jsonToMap(String jsonMap) {
if (jsonMap == null) {
return Collections.emptyMap();
}
return jsonToObject(jsonMap, Map.class);
}
private <T> T jsonToObject(String json, Class<T> requiredType) {
try {
return objectMapper.readValue(json, requiredType);
} catch (IOException e) {
throw new RuntimeException("Could not convert JSON to object: " + e.getMessage(), e);
}
}
}
//
// GraphQLRequest.java
//
package com.example.graphql.endpoint;
import java.util.Map;
/**
* Represents a GraphQL request.
*
* @author Marcel Overdijk
*/
public class GraphQLRequest {
private String query;
private String operationName;
private Map<String, Object> variables;
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public String getOperationName() {
return operationName;
}
public void setOperationName(String operationName) {
this.operationName = operationName;
}
public Map<String, Object> getVariables() {
return variables;
}
public void setVariables(Map<String, Object> variables) {
this.variables = variables;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment