GraphQL IN Golang
- 2. ABOUT ME
Open source contributor
1. Gitea
2. Drone
3. Gin
appleboy @ GitHub
appleboy @ twitter
appleboy @ slideshare
appleboy46 @ facebook
2
- 3. AGENDA
▸ Why we moving API from REST to Graphql?
▸ What is Graphql?
▸ Graphql in Golang (Why we choose Golang)
▸ How to testing Graphql in Golang
▸ Deploy Graphql application
- 11. /**
* @api {get} /scene/:id Request Scene information
* @apiName GetScene
* @apiGroup Scene
*
* @apiParam {Number} id Scenes unique ID.
*
* @apiSuccess {String} title Title of the Scene.
* @apiSuccess {String} desc Description of the Scene.
*/
- 24. type Scene {
name: String!
description: String
items(type: ItemEnum): [Item]
}
enum ItemEnum {
DEVICE,
TEST_DEVICE,
URL
}
- 30. WHY NEED THE INTROSPECTION
▸code generation
▸auto documentation
▸static validation
▸IDE Integration
- 41. // Schema is the GraphQL schema served by the server.
var Schema, _ = graphql.NewSchema(graphql.SchemaConfig{
Query: rootQuery,
Mutation: rootMutation,
Types: types,
})
Query and Mutation
- 44. WHY WE CHOOSE GOLANG?
▸Compiles Into Single Binary
▸Static Type System
▸Performance
▸Easy to Deploy
▸Learning Curve
- 52. func Handler() gin.HandlerFunc {
h := handler.New(&handler.Config{
Schema: &schema.Schema,
Pretty: true,
})
return func(c *gin.Context) {
h.ServeHTTP(c.Writer, c.Request)
}
}
graphql schema
- 54. func Check() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
user, err := authUser(token)
if token != "" && err != nil {
logrus.Errorf("authUser error: %s", err.Error())
}
if user != nil && user.Email != "" {
c.Set("email", user.Email)
c.Set("user_id", user.UserID)
}
ctx := context.WithValue(
c.Request.Context(),
config.ContextKeyUser,
user,
)
c.Request = c.Request.WithContext(ctx)
}
}
store user data
- 55. var Schema, _ = graphql.NewSchema(graphql.SchemaConfig{
Query: rootQuery,
Mutation: rootMutation,
})
var rootMutation = graphql.NewObject(
graphql.ObjectConfig{
Name: "RootMutation",
Description: "Root Mutation",
Fields: graphql.Fields{
"updateUser": &updateUser,
"createScene": &createScene,
"updateScene": &updateScene,
"deleteScene": &deleteScene,
},
})
var rootQuery = graphql.NewObject(
graphql.ObjectConfig{
Name: "RootQuery",
Description: "Root Query",
Fields: graphql.Fields{
"queryUser": &queryUser,
"searchUser": &searchUser,
"queryScene": &queryScene,
},
})
query
mutation
- 58. func (g FormattedError) MarshalJSON() ([]byte, error)
{
m := map[string]interface{}{}
for k, v := range g.Extensions {
m[k] = v
}
m["message"] = g.Message
m["locations"] = g.Locations
return json.Marshal(m)
}
Marshal JSON
custom key value
- 59. func FormatError(err error, arg ...interface{}) gqlerrors.FormattedError
{
switch err := err.(type) {
case gqlerrors.FormattedError:
return err
case *Error:
return gqlerrors.FormattedError{
Message: fmt.Sprintf(err.Error(), arg...),
Extensions: gqlerrors.ErrorExtensions{
"code": err.Type.Code(),
"type": err.Type,
},
}
} custom error
original error
- 64. type Scene struct {
ID int64 `xorm:"pk autoincr" json:"id"`
Image string `json:"image,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
UpdatedAt time.Time `json:"updatedAt,omitempty"`
DeletedAt time.Time `xorm:"deleted"`
// reference
Items []*SceneItem `xorm:"-" json:"items,omitempty"`
User *User `xorm:"-" json:"user,omitempty"`
Role *SceneAccess `xorm:"-" json:"role,omitempty"`
}
Data Model
user role permission
- 65. "user": &graphql.Field{
Type: userType,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
o, ok := p.Source.(*model.Shorten)
if !ok {
return nil, errMissingSource
}
if o.User != nil {
return o.User, nil
}
return getUserFromLoader(p.Context, o.UserID)
},
},
exist in model?
fetch from loader
- 66. GET DATA FROM DATABASE IF NOT EXIST
func userBatch(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
var results []*dataloader.Result
id, _ := helper.GetCacheID(keys[0].String())
user, err := model.GetUserByID(id.(int64))
results = append(results, &dataloader.Result{
Data: user,
Error: err,
})
return results
}
fetch from DB
- 73. -
id: 1
email: foo@gmail.com
full_name: foo
avatar: http://foo.com
avatar_email: foo@gmail.com
-
id: 2
email: bar@gmail.com
full_name: bar
avatar: http://bar.com
avatar_email: bar@gmail.com
real data in DB
- 74. test := T{
Query: `
query QueryShortenURL (
$slug: String!
) {
QueryShortenURL(slug: $slug) {
url
}
}
`,
Schema: Schema,
Expected: &graphql.Result{
Data: map[string]interface{}{
"QueryShortenURL": map[string]interface{}{
"url": "http://example.com",
},
},
},
}
graphql query
expect data
- 75. params := graphql.Params{
Schema: test.Schema,
RequestString: test.Query,
Context: ctx,
VariableValues: map[string]interface{}{
"slug": "abcdef",
},
}
testGraphql(test, params, t)
ctx := newContextWithUser(context.TODO(), user)
user data in context
graphql variable