Graphql dataloader in golang gqlgen

We'll use github.com/vektah/dataloaden to generate dataloader for us.

Generating the code

go get github.com/vektah/dataloaden
mkdir dataloader
cd dataloader
go run github.com/vektah/dataloaden UserLoader string "*github.com/username/project/graph/model.User"
  • notice that you might need put use " between the
  • string the the type of our graphql key.

For example with this graphql schema,

type Car {
   name: String!
   owner: User!
}

type User {
   id: ID!
  name: String!
}

and your model looks like so

package model

type Machine struct {
    ID                 string `json:"id"`
        Username   string   `json:"username"`
    OwnerID      string `json:"owner"`
}

type  User struct {
...
}

in your database. you might store card.ownerId. And then you pass this in your graphql. So it can resolver the User, in this case the ownerId is you key.

const loadersKey = "dataloaders"

// Loaders acts interface for what dataloaders we have
type Loaders struct {
         User        UserLoader
}

type Services struct {
    MachineService machine.Service
    UserService  user.Service
}

func Middleware(s Services, next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := context.WithValue(r.Context(), loadersKey, &Loaders{
            User: UserLoader{
                maxBatch: 500,
                wait:     1 * time.Millisecond,
                fetch: func(usernames []string) ([]*model.User, []error) {
                    queryResults, err := s.UserService.GetUsers(usernames);

                    if err != nil {
                        return nil, []error{fmt.Errorf("Resolver.Users fail to query user from database")}
                    }

                    userByUsername := map[string]*model.User{}

                    for _, q := range queryResults {
                        userByUsername[q.Username] = &model.User{
                            ID:       q.Username,
                            Email:    q.Email,
                            Name:     q.FullName,
                            Username: q.Username,
                        }
                    }

                    users := make([]*model.User, len(usernames))

                    for i, username := range usernames {
                        user := userByUsername[username]
                        users[i] = user
                    }

                    return users, nil
                },
            },
        })
        r = r.WithContext(ctx)

        next.ServeHTTP(w, r)
    })
}

func For(ctx context.Context) *Loaders {
    return ctx.Value(loadersKey).(*Loaders)
}

Notice that I use userByUsername to arrange the positions of the returning users. This is really depends on what your function returns. The point I want to highlight is the position of your keys, in this case usernames, should match the positions of your results, in this case users.

And then you can use this function in your graphql resolver.

For example here's how I use it

func (r *ownerResolver) Owner(ctx context.Context, obj *model.Car) (*model.User, error) {
    return For(ctx).User.Load(obj.OwnerID)
}
Reach me on twitter @muhajirdev
This site is built with NextJS, hosted on Netlify, and uses Github Issues as CMS