-
Notifications
You must be signed in to change notification settings - Fork 842
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
511 additions
and
85 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,25 @@ | ||
# Go GraphQL CRUD example | ||
|
||
Implementation create, read, update and delete on Go | ||
Implement create, read, update and delete on Go. | ||
|
||
To run the program, go to the directory | ||
`cd examples/crud` | ||
To run the program: | ||
|
||
Run the example | ||
`go run main.go` | ||
1. go to the directory: `cd examples/crud` | ||
2. Run the example: `go run main.go` | ||
|
||
## Create | ||
|
||
`http://localhost:8080/product?query=mutation+_{create(name:"Inca Kola",info:"Inca Kola is a soft drink that was created in Peru in 1935 by British immigrant Joseph Robinson Lindley using lemon verbena (wiki)",price:1.99){id,name,info,price}}` | ||
|
||
## Read | ||
Get single product by id | ||
`http://localhost:8080/product?query={product(id:1){name,info,price}}` | ||
|
||
Get product list | ||
`http://localhost:8080/product?query={list{id,name,info,price}}` | ||
* Get single product by id: `http://localhost:8080/product?query={product(id:1){name,info,price}}` | ||
* Get product list: `http://localhost:8080/product?query={list{id,name,info,price}}` | ||
|
||
## Update | ||
|
||
`http://localhost:8080/product?query=mutation+_{update(id:1,price:3.95){id,name,info,price}}` | ||
|
||
## Delete | ||
|
||
`http://localhost:8080/product?query=mutation+_{delete(id:1){id,name,info,price}}` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Go GraphQL SQL null string example | ||
|
||
<a target="_blank" rel="noopener noreferrer" href="https://golang.org/pkg/database/sql/#NullString">database/sql Nullstring</a> implementation, with JSON marshalling interfaces. | ||
|
||
To run the program, go to the directory | ||
`cd examples/sql-nullstring` | ||
|
||
Run the example | ||
`go run main.go` | ||
|
||
## sql.NullString | ||
|
||
On occasion you will encounter sql fields that are nullable, as in | ||
|
||
```sql | ||
CREATE TABLE persons ( | ||
id INT PRIMARY KEY, | ||
name TEXT NOT NULL, | ||
favorite_dog TEXT -- this field can have a NULL value | ||
) | ||
``` | ||
|
||
For the struct | ||
|
||
```golang | ||
import "database/sql" | ||
|
||
type Person struct { | ||
ID int `json:"id" sql:"id"` | ||
Name string `json:"name" sql:"name"` | ||
FavoriteDog sql.NullString `json:"favorite_dog" sql:"favorite_dog"` | ||
} | ||
``` | ||
|
||
But `graphql` would render said field as an object `{{ false}}` or `{{Bulldog true}}`, depending on their validity. | ||
|
||
With this implementation, `graphql` would render the null items as an empty string (`""`), but would be saved in the database as `NULL`, appropriately. | ||
|
||
The pattern can be extended to include other `database/sql` null types. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
package main | ||
|
||
import ( | ||
"database/sql" | ||
"encoding/json" | ||
"fmt" | ||
"github.com/graphql-go/graphql" | ||
"github.com/graphql-go/graphql/language/ast" | ||
"log" | ||
) | ||
|
||
// NullString to be used in place of sql.NullString | ||
type NullString struct { | ||
sql.NullString | ||
} | ||
|
||
// MarshalJSON from the json.Marshaler interface | ||
func (v NullString) MarshalJSON() ([]byte, error) { | ||
if v.Valid { | ||
return json.Marshal(v.String) | ||
} | ||
return json.Marshal(nil) | ||
} | ||
|
||
// UnmarshalJSON from the json.Unmarshaler interface | ||
func (v *NullString) UnmarshalJSON(data []byte) error { | ||
var x *string | ||
if err := json.Unmarshal(data, &x); err != nil { | ||
return err | ||
} | ||
if x != nil { | ||
v.String = *x | ||
v.Valid = true | ||
} else { | ||
v.Valid = false | ||
} | ||
return nil | ||
} | ||
|
||
// NewNullString create a new null string. Empty string evaluates to an | ||
// "invalid" NullString | ||
func NewNullString(value string) *NullString { | ||
var null NullString | ||
if value != "" { | ||
null.String = value | ||
null.Valid = true | ||
return &null | ||
} | ||
null.Valid = false | ||
return &null | ||
} | ||
|
||
// SerializeNullString serializes `NullString` to a string | ||
func SerializeNullString(value interface{}) interface{} { | ||
switch value := value.(type) { | ||
case NullString: | ||
return value.String | ||
case *NullString: | ||
v := *value | ||
return v.String | ||
default: | ||
return nil | ||
} | ||
} | ||
|
||
// ParseNullString parses GraphQL variables from `string` to `CustomID` | ||
func ParseNullString(value interface{}) interface{} { | ||
switch value := value.(type) { | ||
case string: | ||
return NewNullString(value) | ||
case *string: | ||
return NewNullString(*value) | ||
default: | ||
return nil | ||
} | ||
} | ||
|
||
// ParseLiteralNullString parses GraphQL AST value to `NullString`. | ||
func ParseLiteralNullString(valueAST ast.Value) interface{} { | ||
switch valueAST := valueAST.(type) { | ||
case *ast.StringValue: | ||
return NewNullString(valueAST.Value) | ||
default: | ||
return nil | ||
} | ||
} | ||
|
||
// NullableString graphql *Scalar type based of NullString | ||
var NullableString = graphql.NewScalar(graphql.ScalarConfig{ | ||
Name: "NullableString", | ||
Description: "The `NullableString` type repesents a nullable SQL string.", | ||
Serialize: SerializeNullString, | ||
ParseValue: ParseNullString, | ||
ParseLiteral: ParseLiteralNullString, | ||
}) | ||
|
||
/* | ||
CREATE TABLE persons ( | ||
favorite_dog TEXT -- is a nullable field | ||
); | ||
*/ | ||
|
||
// Person noqa | ||
type Person struct { | ||
Name string `json:"name"` | ||
FavoriteDog *NullString `json:"favorite_dog"` // Some people don't like dogs ¯\_(ツ)_/¯ | ||
} | ||
|
||
// PersonType noqa | ||
var PersonType = graphql.NewObject(graphql.ObjectConfig{ | ||
Name: "Person", | ||
Fields: graphql.Fields{ | ||
"name": &graphql.Field{ | ||
Type: graphql.String, | ||
}, | ||
"favorite_dog": &graphql.Field{ | ||
Type: NullableString, | ||
}, | ||
}, | ||
}) | ||
|
||
func main() { | ||
schema, err := graphql.NewSchema(graphql.SchemaConfig{ | ||
Query: graphql.NewObject(graphql.ObjectConfig{ | ||
Name: "Query", | ||
Fields: graphql.Fields{ | ||
"people": &graphql.Field{ | ||
Type: graphql.NewList(PersonType), | ||
Args: graphql.FieldConfigArgument{ | ||
"favorite_dog": &graphql.ArgumentConfig{ | ||
Type: NullableString, | ||
}, | ||
}, | ||
Resolve: func(p graphql.ResolveParams) (interface{}, error) { | ||
dog, dogOk := p.Args["favorite_dog"].(*NullString) | ||
people := []Person{ | ||
Person{Name: "Alice", FavoriteDog: NewNullString("Yorkshire Terrier")}, | ||
// `Bob`'s favorite dog will be saved as null in the database | ||
Person{Name: "Bob", FavoriteDog: NewNullString("")}, | ||
Person{Name: "Chris", FavoriteDog: NewNullString("French Bulldog")}, | ||
} | ||
switch { | ||
case dogOk: | ||
log.Printf("favorite_dog from arguments: %+v", dog) | ||
dogPeople := make([]Person, 0) | ||
for _, p := range people { | ||
if p.FavoriteDog.Valid { | ||
if p.FavoriteDog.String == dog.String { | ||
dogPeople = append(dogPeople, p) | ||
} | ||
} | ||
} | ||
return dogPeople, nil | ||
default: | ||
return people, nil | ||
} | ||
}, | ||
}, | ||
}, | ||
}), | ||
}) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
query := ` | ||
query { | ||
people { | ||
name | ||
favorite_dog | ||
} | ||
}` | ||
queryWithArgument := ` | ||
query { | ||
people(favorite_dog: "Yorkshire Terrier") { | ||
name | ||
favorite_dog | ||
} | ||
}` | ||
r1 := graphql.Do(graphql.Params{ | ||
Schema: schema, | ||
RequestString: query, | ||
}) | ||
r2 := graphql.Do(graphql.Params{ | ||
Schema: schema, | ||
RequestString: queryWithArgument, | ||
}) | ||
if len(r1.Errors) > 0 { | ||
log.Fatal(r1) | ||
} | ||
if len(r2.Errors) > 0 { | ||
log.Fatal(r1) | ||
} | ||
b1, err := json.MarshalIndent(r1, "", " ") | ||
b2, err := json.MarshalIndent(r2, "", " ") | ||
if err != nil { | ||
log.Fatal(err) | ||
|
||
} | ||
fmt.Printf("\nQuery: %+v\n", string(query)) | ||
fmt.Printf("\nResult: %+v\n", string(b1)) | ||
fmt.Printf("\nQuery (with arguments): %+v\n", string(queryWithArgument)) | ||
fmt.Printf("\nResult (with arguments): %+v\n", string(b2)) | ||
} | ||
|
||
/* Output: | ||
Query: | ||
query { | ||
people { | ||
name | ||
favorite_dog | ||
} | ||
} | ||
Result: { | ||
"data": { | ||
"people": [ | ||
{ | ||
"favorite_dog": "Yorkshire Terrier", | ||
"name": "Alice" | ||
}, | ||
{ | ||
"favorite_dog": "", | ||
"name": "Bob" | ||
}, | ||
{ | ||
"favorite_dog": "French Bulldog", | ||
"name": "Chris" | ||
} | ||
] | ||
} | ||
} | ||
Query (with arguments): | ||
query { | ||
people(favorite_dog: "Yorkshire Terrier") { | ||
name | ||
favorite_dog | ||
} | ||
} | ||
Result (with arguments): { | ||
"data": { | ||
"people": [ | ||
{ | ||
"favorite_dog": "Yorkshire Terrier", | ||
"name": "Alice" | ||
} | ||
] | ||
} | ||
} | ||
*/ |
Oops, something went wrong.