Skip to content

Commit ac89766

Browse files
author
Tobias Fuhrimann
committed
Add GraphiQL support
1 parent fab5288 commit ac89766

File tree

3 files changed

+215
-13
lines changed

3 files changed

+215
-13
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Golang HTTP.Handler for [graphl-go](https://github.com/graphql-go/graphql)
44

55
### Notes:
6-
This is based on alpha version of `graphql-go` and `graphql-relay-go`.
6+
This is based on alpha version of `graphql-go` and `graphql-relay-go`.
77
Be sure to watch both repositories for latest changes.
88

99
### Usage
@@ -20,12 +20,13 @@ func main() {
2020

2121
// define GraphQL schema using relay library helpers
2222
schema := graphql.NewSchema(...)
23-
23+
2424
h := handler.New(&handler.Config{
2525
Schema: &schema,
2626
Pretty: true,
27+
GraphiQL: true,
2728
})
28-
29+
2930
// serve HTTP
3031
http.Handle("/graphql", h)
3132
http.ListenAndServe(":8080", nil)

graphiql.go

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package handler
2+
3+
import (
4+
"encoding/json"
5+
"html/template"
6+
"net/http"
7+
8+
"github.com/graphql-go/graphql"
9+
)
10+
11+
// page is the page data structure of the rendered GraphiQL page
12+
type graphiqlPage struct {
13+
GraphiqlVersion string
14+
QueryString string
15+
ResultString string
16+
VariablesString string
17+
OperationName string
18+
}
19+
20+
// renderGraphiQL renders the GraphiQL GUI
21+
func renderGraphiQL(w http.ResponseWriter, params graphql.Params) {
22+
t := template.New("GraphiQL")
23+
t, err := t.Parse(graphiqlTemplate)
24+
if err != nil {
25+
http.Error(w, err.Error(), http.StatusInternalServerError)
26+
return
27+
}
28+
29+
// Create variables string
30+
vars, err := json.MarshalIndent(params.VariableValues, "", " ")
31+
if err != nil {
32+
http.Error(w, err.Error(), http.StatusInternalServerError)
33+
return
34+
}
35+
varsString := string(vars)
36+
if varsString == "null" {
37+
varsString = ""
38+
}
39+
40+
// Create result string
41+
result, err := json.MarshalIndent(graphql.Do(params), "", " ")
42+
resString := string(result)
43+
44+
p := graphiqlPage{
45+
GraphiqlVersion: graphiqlVersion,
46+
QueryString: params.RequestString,
47+
ResultString: resString,
48+
VariablesString: varsString,
49+
OperationName: params.OperationName,
50+
}
51+
52+
err = t.ExecuteTemplate(w, "index", p)
53+
if err != nil {
54+
http.Error(w, err.Error(), http.StatusInternalServerError)
55+
}
56+
return
57+
}
58+
59+
// graphiqlVersion is the current version of GraphiQL
60+
const graphiqlVersion = "0.11.3"
61+
62+
// tmpl is the page template to render GraphiQL
63+
const graphiqlTemplate = `
64+
{{ define "index" }}
65+
<!--
66+
The request to this GraphQL server provided the header "Accept: text/html"
67+
and as a result has been presented GraphiQL - an in-browser IDE for
68+
exploring GraphQL.
69+
70+
If you wish to receive JSON, provide the header "Accept: application/json" or
71+
add "&raw" to the end of the URL within a browser.
72+
-->
73+
<!DOCTYPE html>
74+
<html>
75+
<head>
76+
<meta charset="utf-8" />
77+
<title>GraphiQL</title>
78+
<meta name="robots" content="noindex" />
79+
<style>
80+
html, body {
81+
height: 100%;
82+
margin: 0;
83+
overflow: hidden;
84+
width: 100%;
85+
}
86+
</style>
87+
<link href="//cdn.jsdelivr.net/npm/graphiql@{{ .GraphiqlVersion }}/graphiql.css" rel="stylesheet" />
88+
<script src="//cdn.jsdelivr.net/fetch/0.9.0/fetch.min.js"></script>
89+
<script src="//cdn.jsdelivr.net/react/15.4.2/react.min.js"></script>
90+
<script src="//cdn.jsdelivr.net/react/15.4.2/react-dom.min.js"></script>
91+
<script src="//cdn.jsdelivr.net/npm/graphiql@{{ .GraphiqlVersion }}/graphiql.min.js"></script>
92+
</head>
93+
<body>
94+
<script>
95+
// Collect the URL parameters
96+
var parameters = {};
97+
window.location.search.substr(1).split('&').forEach(function (entry) {
98+
var eq = entry.indexOf('=');
99+
if (eq >= 0) {
100+
parameters[decodeURIComponent(entry.slice(0, eq))] =
101+
decodeURIComponent(entry.slice(eq + 1));
102+
}
103+
});
104+
105+
// Produce a Location query string from a parameter object.
106+
function locationQuery(params) {
107+
return '?' + Object.keys(params).filter(function (key) {
108+
return Boolean(params[key]);
109+
}).map(function (key) {
110+
return encodeURIComponent(key) + '=' +
111+
encodeURIComponent(params[key]);
112+
}).join('&');
113+
}
114+
115+
// Derive a fetch URL from the current URL, sans the GraphQL parameters.
116+
var graphqlParamNames = {
117+
query: true,
118+
variables: true,
119+
operationName: true
120+
};
121+
122+
var otherParams = {};
123+
for (var k in parameters) {
124+
if (parameters.hasOwnProperty(k) && graphqlParamNames[k] !== true) {
125+
otherParams[k] = parameters[k];
126+
}
127+
}
128+
var fetchURL = locationQuery(otherParams);
129+
130+
// Defines a GraphQL fetcher using the fetch API.
131+
function graphQLFetcher(graphQLParams) {
132+
return fetch(fetchURL, {
133+
method: 'post',
134+
headers: {
135+
'Accept': 'application/json',
136+
'Content-Type': 'application/json'
137+
},
138+
body: JSON.stringify(graphQLParams),
139+
credentials: 'include',
140+
}).then(function (response) {
141+
return response.text();
142+
}).then(function (responseBody) {
143+
try {
144+
return JSON.parse(responseBody);
145+
} catch (error) {
146+
return responseBody;
147+
}
148+
});
149+
}
150+
151+
// When the query and variables string is edited, update the URL bar so
152+
// that it can be easily shared.
153+
function onEditQuery(newQuery) {
154+
parameters.query = newQuery;
155+
updateURL();
156+
}
157+
158+
function onEditVariables(newVariables) {
159+
parameters.variables = newVariables;
160+
updateURL();
161+
}
162+
163+
function onEditOperationName(newOperationName) {
164+
parameters.operationName = newOperationName;
165+
updateURL();
166+
}
167+
168+
function updateURL() {
169+
history.replaceState(null, null, locationQuery(parameters));
170+
}
171+
172+
// Render <GraphiQL /> into the body.
173+
ReactDOM.render(
174+
React.createElement(GraphiQL, {
175+
fetcher: graphQLFetcher,
176+
onEditQuery: onEditQuery,
177+
onEditVariables: onEditVariables,
178+
onEditOperationName: onEditOperationName,
179+
query: {{ .QueryString }},
180+
response: {{ .ResultString }},
181+
variables: {{ .VariablesString }},
182+
operationName: {{ .OperationName }},
183+
}),
184+
document.body
185+
);
186+
</script>
187+
</body>
188+
</html>
189+
{{ end }}
190+
`

handler.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ const (
2020

2121
type Handler struct {
2222
Schema *graphql.Schema
23-
24-
pretty bool
23+
24+
pretty bool
25+
graphiql bool
2526
}
2627
type RequestOptions struct {
2728
Query string `json:"query" url:"query" schema:"query"`
@@ -129,7 +130,14 @@ func (h *Handler) ContextHandler(ctx context.Context, w http.ResponseWriter, r *
129130
}
130131
result := graphql.Do(params)
131132

132-
133+
if h.graphiql {
134+
acceptHeader := r.Header.Get("Accept")
135+
if !strings.Contains(acceptHeader, "application/json") && strings.Contains(acceptHeader, "text/html") {
136+
renderGraphiQL(w, params)
137+
return
138+
}
139+
}
140+
133141
if h.pretty {
134142
w.WriteHeader(http.StatusOK)
135143
buff, _ := json.MarshalIndent(result, "", "\t")
@@ -138,7 +146,7 @@ func (h *Handler) ContextHandler(ctx context.Context, w http.ResponseWriter, r *
138146
} else {
139147
w.WriteHeader(http.StatusOK)
140148
buff, _ := json.Marshal(result)
141-
149+
142150
w.Write(buff)
143151
}
144152
}
@@ -149,14 +157,16 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
149157
}
150158

151159
type Config struct {
152-
Schema *graphql.Schema
153-
Pretty bool
160+
Schema *graphql.Schema
161+
Pretty bool
162+
GraphiQL bool
154163
}
155164

156165
func NewConfig() *Config {
157166
return &Config{
158-
Schema: nil,
159-
Pretty: true,
167+
Schema: nil,
168+
Pretty: true,
169+
GraphiQL: true,
160170
}
161171
}
162172

@@ -169,7 +179,8 @@ func New(p *Config) *Handler {
169179
}
170180

171181
return &Handler{
172-
Schema: p.Schema,
173-
pretty: p.Pretty,
182+
Schema: p.Schema,
183+
pretty: p.Pretty,
184+
graphiql: p.GraphiQL,
174185
}
175186
}

0 commit comments

Comments
 (0)