Go Flavoured GraphQL module for implementing fast, type safe GraphQL services. Extracted from sensu/sensu-go. Represents code in production by organizations running Sensu version 2.
For those familiar with implementing a gRPC service in golang should feel right at home.
-
Add new type / field definition(s).
# ./graphqlservice/schema/dog.graphql """ Reference to a resource on the web. """ scalar URL """ A Dog are the best pets. """ type Dog implements Named { name: String! profilePicture(size: Size): URL friends: [Pet]! } interface Named { name: String! } type Rabbit implements Named { name: String! friends: [Pet]! isFluffy: Boolean! } input Size { width: Int height: Int density: float } union Pet = Dog | Rabbit
-
Next you'll want to generate the Go code for these new types. To do this we use the
gengraphql
script.Run:
gengraphql ./path-to-my-schema
-
Next we need to tell our service how the types themselves are implemented. To give an example, when a user selects a dog's friends, the service needs to know how to retrieve those details so that it can be display them to the user.
An example implementation:
// ./graphqlservice/dog.go type dogFieldResolvers struct { // the autogenerated "aliases" use reflection under the hood to implement most // most the field resolvers; this allows us to write a bit less code with a // small runtime cost. *schema.DogAliases controller FriendsController logger logrus.Entry } // Use controller to retrieve our dog's friends. func (fr *dogFieldResolvers) Friends(p graphql.ResolveParams) (interface{}, error) { dog := p.Source.(*types.Dog) ctx := p.Context friends, err := fr.controller.ListFriendos(ctx, dog) return friends, err } // IsTypeOf is used to determine if a given value is associated with the Dog type func (fr *dogFieldResolvers) IsTypeOf(s interface{}, p graphql.IsTypeOfParams) bool { _, ok := s.(*types.Dog) return ok }
-
Finally we need to register the new type(s) and any of the implementation details with our service.
// ./graphqlservice/service.go fund NewService(c Config) *Service { // ... dogImpl := dogFieldResolvers{controller: ..., logger: ...} // ... // ... schema.RegisterDog(svc, dogImpl) // include fieldresolvers. schema.RegisterSize(svc) // unlike object type's inputs do not require any additonal implemtation details. // ... // configures registered types and implementations so that service is ready to // accept queries. service.Reconfigure() return service }
Mutations are how the client modifies the server-side data.
-
Sensu follow's Relay's mutation conventions. Each mutation should consist of three elements. An input object type that describes the parameters to the mutation, an object type that describes the return values, and finally a field on the
Mutation
to be used as the entry point.# mutations.graphql type Mutation { # ... addRole(inputs: AddRoleInput) AddRolePayload # ... } input AddRoleInput { # Used by a client to keep track of in-flight mutations clientMutationId: String! userId: ID! roleId: ID! } type AddRolePayload { clientMutationId: String! user: User! role: Role! }
-
Fields should not be removed until we can be confident that no clients are using the field.
-
GraphQL supports @deprecated directive for marking a field as deprecated.
type MyType { one: String! @deprecated two: String! @deprecated(reason: "Two is bad number.") three: String! }
- Type Definitions live in the
schema
package, and use the file extension.graphql
.- When the type(s) they match an internal type defined in the
types
package the filenames should ideally match. (Eg.entity.go
entity.graphql
.) - Ideally all types and fields are
- When the type(s) they match an internal type defined in the
- FieldResolvers live in the
graphql
package.- Filenames should match the same name of the graphql file it is implementing.