Is there any documentation or guide for resolvers? The starwars example seems pretty convoluted.
Can you be a little more specific on what is confusing to you about the example given? I could take a shot at writing an example based off of an approach I've taken, but it would help to know which points are most unclear.
I just need to see how mutations work. How to expose the mutation over the graphql api and then how to make a function, like with anonymous functions/lambda expressions, or calling a method.
I'm figuring it out as I go but I'll contribute 1-2 examples to this repo in the next 2 monthseventually, with persistence and authentication+authorization. (I have those things working right now, but want some time to check if my implementations have gaps)
For now, this is what I've been doing:
schema.graphql
type Schema {
query: Query
mutation: Mutation
}
type Query {
foo(id: ID!): Foo
}
type Mutation {
createFoo(bar: String!, baz: String): Foo
}
type Foo {
id: ID!
bar: String!
baz: String
}
main.go
func main() {
db, _ db.Open("/DSN")
rawSchema, _ := ioutil.ReadFile("/path/to/schema.graphql")
parsedSchema, _ := graphql.ParseSchema(string(rawSchema), NewResolver(db))
if os.Getenv("production") != "true" {
http.HandleFunc("/", graphiqlHandler)
}
http.Handle("/graphql", Middleware(injectViewerToContext(&relay.Handler{Schema: parsedSchema})))
http.ListenAndServe(":8080", nil)
}
func injectViewerToContext(next http.Handler) http.Handler {
return func(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
viewerID, authenticated := authenticate(tokenString) // custom 401 function
if !authenticated {
w.Write(http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), "Authorization", viewerID))) // could pass a custom viewer type with roles, etc
}
}
resolver.go
type Resolver { db: *sql.DB }
func NewResolver(db *sql.DB) *Resolver {
return &Resolver{db: db}
}
func (r *Resolver) Foo(ctx context.Context, args *struct{ ID string }) (*FooResolver, error) {
viewerID, _ := context.Value("auth", ctx).(uuid.UUID)
return NewFooResolver(viewerID, args.ID, r.db)
}
func (r *Resolver) CreateFoo(ctx context.Context, args *struct{
Bar string
Baz *string
}) (*FooResolver, error) {
viewerID, _ := context.Value("auth", ctx).(uuid.UUID)
id, _ := NewFoo(viewerID, args.Bar, args.Baz, r.db)
return NewFooResolver(viewerID, id, r.db)
}
foo.go
func NewFoo(viewerID uuid.UUID, bar string, bazPtr *string, db *sql.DB) (*FooResolver, error) {
// do some 403
if !mayCreateFoo(viewerID) {
return nil, errors.New("403")
}
id := uuid.NewV1()
tx, _ := db.Begin()
_, _ = tx.Exec("INSERT INTO foo_bar (id, bar) VALUES (?, ?)", id.Bytes(), args.Bar)
if bazPtr != nil {
_, _ = tx.Exec("INSERT INTO foo_baz (id, baz) VALUES (?, ?)", id.Bytes(), *args.Baz)
}
_ = tx.Commit()
return NewFooResolver(viewerID, id, db), nil
}
type FooResolver struct {
viewerID uuid.UUID
id uuid.UUID
db *sql.DB
}
func NewFooResolver(viewerID, id uuid.UUID, db *sql.DB) (*FooResolver, error) {
return &FooResolver{
viewerID: viewerID,
id: id,
db: db,
}, nil
}
func (r *FooResolver) ID() graphql.ID {
return graphql.ID(r.id.String())
}
func (r *FooResolver) Bar() (string, error) {
// do some 403
if !mayViewBar(r.viewerID, id) {
return "", errors.New("403")
}
var bar string
_ := r.db.QueryRow("SELECT bar FROM foo_bar WHERE id=?", r.id.Bytes()).Scan(&bar)
return bar, nil
}
func (r *FooResolver) Baz) (*string, error) {
// do some 403
if !mayViewBaz(r.viewerID, id) {
return "", errors.New("403")
}
var baz string
_ := r.db.QueryRow("SELECT baz FROM foo_baz WHERE id=?", r.id.Bytes()).Scan(&baz)
return &baz, nil
}
Notes:
nullable, then your resolver must return a pointer, with a nil pointer resolving to null401 before enterring graphql and putting your 403 logic in your custom type's constructor (not the resolver, but your domain types). Make your struct's fields unexported and have only one exported constructor. Now, on your exported methods on your custom type, handle 403 within that so that anytime you have data, you know that it's already been 403'd. That would mean moving the SELECT queries out of Bar() and Baz(), and moving them into the methods on that custom struct.One other thing I've noticed while poking around the code is that there aren't many comments in general. It'd be really useful in general to at least have godoc comments. I'd like to be able to contribute to the code if possible, but it makes it that much more difficult to start without any docs or comments.
I'd be happy to contribute to a documentation, especially if there were clear governance and guidelines !
A tutorial would also be helpful, as obviously the hard part is getting started, even though the starwars example aswell as this repo help a lot.
I've been working on a closer-to-real-life example that you may find helpful: https://github.com/tonyghita/graphql-go-example
It just wraps the API at https://swapi.co and implements some patterns I've found useful in my year of experience working with GraphQL+Go.
I haven't had time to go through and document all the concepts, but hopefully it helps someone out as it is.
@tonyghita well that's the repo I linked in my previous message!
Thanks it indeed helped a lot, I greatly inspired from your work!
@SCKelemen Have you found out how mutations work? Any idea what that query looks like?
I've been looking at all the examples trying to implement whatever I can but I'm getting errors saying all fields in args need to be pointers. Has this changed?
@tendolukwago I actually haven't. I haven't been working with GraphQL lately. I've mostly switched to gRPC. I've had less issues, and the documentation is better. (It's slightly different, though) I may be revisiting this in a few months, depending on how my priorities are set, by my team.
@abradley2 i think you're getting that error because fields in your schema (files with the .graphql extension) aren't required or are left optional (Field! is required, Field is optional).
@SCKelemen interesting...haven't had a chance to check it out yet, but this ended up working for me (via POST request body):
{ "query": "mutation{create(name:\"Name\"){name, created}}"}
Most helpful comment
I'm figuring it out as I go but I'll contribute 1-2 examples to this repo
in the next 2 monthseventually, with persistence and authentication+authorization. (I have those things working right now, but want some time to check if my implementations have gaps)For now, this is what I've been doing:
schema.graphqlmain.goresolver.gofoo.goNotes:
nullable, then your resolver must return apointer, with anil pointerresolving tonull401before enterring graphql and putting your403logic in your custom type's constructor (not the resolver, but your domain types). Make your struct's fields unexported and have only one exported constructor. Now, on your exported methods on your custom type, handle403within that so that anytime you have data, you know that it's already been403'd. That would mean moving theSELECTqueries out ofBar()andBaz(), and moving them into the methods on that custom struct.