Skip to content

Commit

Permalink
Add httpdynamic examples
Browse files Browse the repository at this point in the history
This example shows how we can generate GraphQL schema on runtime.

Credit to Rob Smith from Uber @ Seattle for the dynamic reloading part.
  • Loading branch information
motiejus committed Jan 21, 2017
1 parent 22050ee commit 36a6dd6
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 0 deletions.
40 changes: 40 additions & 0 deletions examples/httpdynamic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Basically, if we have `data.json` like this:

[
{ "id": "1", "name": "Dan" },
{ "id": "2", "name": "Lee" },
{ "id": "3", "name": "Nick" }
]

... and `go run main.go`, we can query records:

$ curl -g 'http://localhost:8080/graphql?query={user(name:"Dan"){id}}'
{"data":{"user":{"id":"1"}}}

... now let's give Dan a surname:

[
{ "id": "1", "name": "Dan", "surname": "Jones" },
{ "id": "2", "name": "Lee" },
{ "id": "3", "name": "Nick" }
]

... and kick the server:

kill -SIGUSR1 52114

And ask for Dan's surname:

$ curl -g 'http://localhost:8080/graphql?query={user(name:"Dan"){id,surname}}'
{"data":{"user":{"id":"1","surname":"Jones"}}}

... or ask Jones's name and ID:

$ curl -g 'http://localhost:8080/graphql?query={user(surname:"Jones"){id,name}}'
{"data":{"user":{"id":"1","name":"Dan"}}}

If you look at `main.go`, the file is not field-aware. That is, all it knows is
how to work with `[]map[string]string` type.

With this, we are not that far from exposing dynamic fields and filters which
fully depend on what we have stored, all without changing our tooling.
15 changes: 15 additions & 0 deletions examples/httpdynamic/data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[
{
"id": "1",
"name": "Dan",
"surname": "Jones"
},
{
"id": "2",
"name": "Lee"
},
{
"id": "3",
"name": "Nick"
}
]
138 changes: 138 additions & 0 deletions examples/httpdynamic/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package main

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"

"github.com/graphql-go/graphql"
)

/*****************************************************************************/
/* Shared data variables to allow dynamic reloads
/*****************************************************************************/

var schema graphql.Schema

const jsonDataFile = "data.json"

func handleSIGUSR1(c chan os.Signal) {
for {
<-c
fmt.Printf("Caught SIGUSR1. Reloading %s\n", jsonDataFile)
err := importJSONDataFromFile(jsonDataFile)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
return
}
}
}

func filterUser(data []map[string]interface{}, args map[string]interface{}) map[string]interface{} {
for _, user := range data {
for k, v := range args {
if user[k] != v {
goto nextuser
}
return user
}

nextuser:
}
return nil
}

func executeQuery(query string, schema graphql.Schema) *graphql.Result {
result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
})
if len(result.Errors) > 0 {
fmt.Printf("wrong result, unexpected errors: %v\n", result.Errors)
}
return result
}

func importJSONDataFromFile(fileName string) error {
content, err := ioutil.ReadFile(fileName)
if err != nil {
return err
}

var data []map[string]interface{}

err = json.Unmarshal(content, &data)
if err != nil {
return err
}

fields := make(graphql.Fields)
args := make(graphql.FieldConfigArgument)
for _, item := range data {
for k := range item {
fields[k] = &graphql.Field{
Type: graphql.String,
}
args[k] = &graphql.ArgumentConfig{
Type: graphql.String,
}
}
}

var userType = graphql.NewObject(
graphql.ObjectConfig{
Name: "User",
Fields: fields,
},
)

var queryType = graphql.NewObject(
graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"user": &graphql.Field{
Type: userType,
Args: args,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return filterUser(data, p.Args), nil
},
},
},
})

schema, _ = graphql.NewSchema(
graphql.SchemaConfig{
Query: queryType,
},
)

return nil
}

func main() {
// Catch SIGUSR1 and reload the data file
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGUSR1)
go handleSIGUSR1(c)

err := importJSONDataFromFile(jsonDataFile)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
return
}

http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
result := executeQuery(r.URL.Query()["query"][0], schema)
json.NewEncoder(w).Encode(result)
})

fmt.Println("Now server is running on port 8080")
fmt.Println("Test with Get : curl -g 'http://localhost:8080/graphql?query={user(name:\"Dan\"){id,surname}}'")
fmt.Printf("Reload json file : kill -SIGUSR1 %s\n", strconv.Itoa(os.Getpid()))
http.ListenAndServe(":8080", nil)
}

0 comments on commit 36a6dd6

Please sign in to comment.