Comments (11)
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:
- It looks like type-asserting the viewer from context is the "best" available way right now
- If your type in your graphql schema is
nullable
, then your resolver must return apointer
, with anil pointer
resolving tonull
- The above is a fairly naive implementation. Still want to add caching, for one. I've exhausted my database's connections quite quickly nesting 5 layers in. I'm really interested in some type of middleware layer that adds onto dataloader by also parsing the SQL queries and combining columns. Hopefully, some of that goes into Vitess' query optimizer too
- One practice I think is good that I go partway towards is to handle your
401
before enterring graphql and putting your403
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, handle403
within that so that anytime you have data, you know that it's already been403
'd. That would mean moving theSELECT
queries out ofBar()
andBaz()
, and moving them into the methods on that custom struct.
from graphql-go.
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.
from graphql-go.
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.
from graphql-go.
@tonyghita well that's the repo I linked in my previous message!
Thanks it indeed helped a lot, I greatly inspired from your work!
from graphql-go.
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.
from graphql-go.
@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}}"}
from graphql-go.
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.
from graphql-go.
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.
from graphql-go.
@SCKelemen Have you found out how mutations work? Any idea what that query looks like?
from graphql-go.
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?
from graphql-go.
@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.
from graphql-go.
Related Issues (20)
- Question on query string parsing HOT 1
- Rename types package
- Add enum example
- Allow directives without a Go visitor counterpart
- Allow field names starting with a leading `_`
- Allow struct fields which are functions
- Remove _service from the introspection and it should be opt-in
- Apollo Federation subgraph example
- The query resolver can't receive the context HOT 7
- `MustParseSchema` panics with nullable argument Directives HOT 6
- Subscription fails with missing method error HOT 1
- 2023/03/29 07:14:42 Error: The data source arguments are not valid HOT 3
- query parser bug HOT 1
- MongoDB _id field HOT 3
- Issue with negative numbers HOT 3
- Optimized batch loading without blocking HOT 2
- Please add simple example without react, relay, http etc... HOT 2
- [Bug] GraphQL server returns interface type for __typename when UseFieldResolvers is enabled HOT 1
- Directives import in example code seems wrong HOT 3
- New release with directive support HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from graphql-go.