diff --git a/README.md b/README.md
index 4ba1ef4..a6986c5 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
Golang HTTP.Handler for [graphl-go](https://github.com/graphql-go/graphql)
### Notes:
-This is based on alpha version of `graphql-go` and `graphql-relay-go`.
+This is based on alpha version of `graphql-go` and `graphql-relay-go`.
Be sure to watch both repositories for latest changes.
### Usage
@@ -20,12 +20,13 @@ func main() {
// define GraphQL schema using relay library helpers
schema := graphql.NewSchema(...)
-
+
h := handler.New(&handler.Config{
Schema: &schema,
Pretty: true,
+ GraphiQL: true,
})
-
+
// serve HTTP
http.Handle("/graphql", h)
http.ListenAndServe(":8080", nil)
diff --git a/graphiql.go b/graphiql.go
new file mode 100644
index 0000000..ace949b
--- /dev/null
+++ b/graphiql.go
@@ -0,0 +1,199 @@
+package handler
+
+import (
+ "encoding/json"
+ "html/template"
+ "net/http"
+
+ "github.com/graphql-go/graphql"
+)
+
+// page is the page data structure of the rendered GraphiQL page
+type graphiqlPage struct {
+ GraphiqlVersion string
+ QueryString string
+ ResultString string
+ VariablesString string
+ OperationName string
+}
+
+// renderGraphiQL renders the GraphiQL GUI
+func renderGraphiQL(w http.ResponseWriter, params graphql.Params) {
+ t := template.New("GraphiQL")
+ t, err := t.Parse(graphiqlTemplate)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ // Create variables string
+ vars, err := json.MarshalIndent(params.VariableValues, "", " ")
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ varsString := string(vars)
+ if varsString == "null" {
+ varsString = ""
+ }
+
+ // Create result string
+ var resString string
+ if params.RequestString == "" {
+ resString = ""
+ } else {
+ result, err := json.MarshalIndent(graphql.Do(params), "", " ")
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ resString = string(result)
+ }
+
+ p := graphiqlPage{
+ GraphiqlVersion: graphiqlVersion,
+ QueryString: params.RequestString,
+ ResultString: resString,
+ VariablesString: varsString,
+ OperationName: params.OperationName,
+ }
+
+ err = t.ExecuteTemplate(w, "index", p)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ return
+}
+
+// graphiqlVersion is the current version of GraphiQL
+const graphiqlVersion = "0.11.3"
+
+// tmpl is the page template to render GraphiQL
+const graphiqlTemplate = `
+{{ define "index" }}
+
+
+
+
+
+ GraphiQL
+
+
+
+
+
+
+
+
+
+
+
+
+{{ end }}
+`
diff --git a/graphiql_test.go b/graphiql_test.go
new file mode 100644
index 0000000..25ad929
--- /dev/null
+++ b/graphiql_test.go
@@ -0,0 +1,90 @@
+package handler_test
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "github.com/graphql-go/graphql/testutil"
+ "github.com/graphql-go/handler"
+)
+
+func TestRenderGraphiQL(t *testing.T) {
+ cases := map[string]struct {
+ graphiqlEnabled bool
+ accept string
+ url string
+ expectedStatusCode int
+ expectedContentType string
+ expectedBodyContains string
+ }{
+ "renders GraphiQL": {
+ graphiqlEnabled: true,
+ accept: "text/html",
+ expectedStatusCode: http.StatusOK,
+ expectedContentType: "text/html; charset=utf-8",
+ expectedBodyContains: "",
+ },
+ "doesn't render graphiQL if turned off": {
+ graphiqlEnabled: false,
+ accept: "text/html",
+ expectedStatusCode: http.StatusOK,
+ expectedContentType: "application/json; charset=utf-8",
+ },
+ "doesn't render GraphiQL if Content-Type application/json is present": {
+ graphiqlEnabled: true,
+ accept: "application/json,text/html",
+ expectedStatusCode: http.StatusOK,
+ expectedContentType: "application/json; charset=utf-8",
+ },
+ "doesn't render GraphiQL if Content-Type text/html is not present": {
+ graphiqlEnabled: true,
+ expectedStatusCode: http.StatusOK,
+ expectedContentType: "application/json; charset=utf-8",
+ },
+ "doesn't render GraphiQL if 'raw' query is present": {
+ graphiqlEnabled: true,
+ accept: "text/html",
+ url: "?raw",
+ expectedStatusCode: http.StatusOK,
+ expectedContentType: "application/json; charset=utf-8",
+ },
+ }
+
+ for tcID, tc := range cases {
+ t.Run(tcID, func(t *testing.T) {
+ req, err := http.NewRequest(http.MethodGet, tc.url, nil)
+ if err != nil {
+ t.Error(err)
+ }
+
+ req.Header.Set("Accept", tc.accept)
+
+ h := handler.New(&handler.Config{
+ Schema: &testutil.StarWarsSchema,
+ GraphiQL: tc.graphiqlEnabled,
+ })
+
+ rr := httptest.NewRecorder()
+
+ h.ServeHTTP(rr, req)
+ resp := rr.Result()
+
+ statusCode := resp.StatusCode
+ if statusCode != tc.expectedStatusCode {
+ t.Fatalf("%s: wrong status code, expected %v, got %v", tcID, tc.expectedStatusCode, statusCode)
+ }
+
+ contentType := resp.Header.Get("Content-Type")
+ if contentType != tc.expectedContentType {
+ t.Fatalf("%s: wrong content type, expected %s, got %s", tcID, tc.expectedContentType, contentType)
+ }
+
+ body := rr.Body.String()
+ if !strings.Contains(body, tc.expectedBodyContains) {
+ t.Fatalf("%s: wrong body, expected %s to contain %s", tcID, body, tc.expectedBodyContains)
+ }
+ })
+ }
+}
diff --git a/handler.go b/handler.go
index 4494ee9..6666f54 100644
--- a/handler.go
+++ b/handler.go
@@ -21,7 +21,8 @@ const (
type Handler struct {
Schema *graphql.Schema
- pretty bool
+ pretty bool
+ graphiql bool
}
type RequestOptions struct {
Query string `json:"query" url:"query" schema:"query"`
@@ -129,8 +130,17 @@ func (h *Handler) ContextHandler(ctx context.Context, w http.ResponseWriter, r *
}
result := graphql.Do(params)
+ if h.graphiql {
+ acceptHeader := r.Header.Get("Accept")
+ _, raw := r.URL.Query()["raw"]
+ if !raw && !strings.Contains(acceptHeader, "application/json") && strings.Contains(acceptHeader, "text/html") {
+ renderGraphiQL(w, params)
+ return
+ }
+ }
+
// use proper JSON Header
- w.Header().Add("Content-Type", "application/json")
+ w.Header().Add("Content-Type", "application/json; charset=utf-8")
if h.pretty {
w.WriteHeader(http.StatusOK)
@@ -151,14 +161,16 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
type Config struct {
- Schema *graphql.Schema
- Pretty bool
+ Schema *graphql.Schema
+ Pretty bool
+ GraphiQL bool
}
func NewConfig() *Config {
return &Config{
- Schema: nil,
- Pretty: true,
+ Schema: nil,
+ Pretty: true,
+ GraphiQL: true,
}
}
@@ -171,7 +183,8 @@ func New(p *Config) *Handler {
}
return &Handler{
- Schema: p.Schema,
- pretty: p.Pretty,
+ Schema: p.Schema,
+ pretty: p.Pretty,
+ graphiql: p.GraphiQL,
}
}