Chapter 5 GraphQL: Operations

5.1 Introduction

GraphQL provides us 3 different operations:

  • Queries: Operation to retrieve data from the server.
  • Mutations: CUD operations: Create, Update and Delete.
  • Subscriptions: Create and maintain real time connection to the server. This enables the client to get immediate information about related events. Basically, a client subscribes to an event in the server, and whenever that event occurs, the server sends data to the client.

In our workshop.graphql we will find already implemented operations.

5.2 Code

There’s no difference between implement a query or a mutation.

Let’s see how to implement getMonkey (query) and addMonkey (mutation).

database/model.go

type MonkeyTable struct {
    Id int
    Family string
}

graphql/model/models.go

type Monkey struct {
    Monkey database.MonkeyTable
}

gqlgen.yml

...
models:
  Monkey:
    model: workshop-graphql-go/graphql/model.Monkey
...

then we must run make generate

graphql/resolver/query.go

func (r *Query) GetMonkey(ctx context.Context, monkeyID int) (*model.Monkey, error) {
    monkey, err := GetDB(ctx).GetMonkey(monkeyID)
    if err != nil {
        return nil, err
    }
    return &model.MOnkey{Monkey: monkey}, nil
}

graphql/resolver/mutation.go

func (r *Mutation) addMonkey(ctx context.Context, request *model.MonkeyRequest) (*model.Monkey, error) {
    monkey := database.MonkeyTable{
        Family: request.Family,
    }
    m, err := GetDB(ctx).InsertMonkey(monkey)
    if err != nil {
        return nil, err
    }
    return &model.MOnkey{Monkey: *m}, nil
}

If we have a look at generated file in graphql/exec/generated.go we see that there’s an interface MonkeyResolver which we need to implement.

graphql/resolver/monkey.go

type MonkeyResolver struct{}

func (r *MonkeyResolver) ID(ctx context.Context, obj *model.Monkey) (int, error) {
    return obj.Monkey.Id, nil
}

func (r *MonkeyResolver) FullName(ctx context.Context, obj *model.Monkey) (string, error) {
    return obj.Monkey.Family, nil
}

Subscriptions looks a little bit different because we need to subscribe to an event. The methods must be defined in a class that implements GraphQLSubscriptionResolver.

The first difference of implementing a subscription is that the returned value is a channel.

graphql/resolver/subscription.go

func (resolver *Resolver) ListenMonkeys(ctx context.Context) (<-chan *model.Monkey,error) {
    resolver.MonkeysSubscriber <- &MonkeysSubscriber{events: resolver.MonkeysEvents, stop: ctx.Done()}
    return resolver.MonkeysSubscriber
}

If we have a look at resolver/pubsub.go we find how subscriptions are handled in our API.

Have a look at already implemented queries,mutations and subscriptions in this project.

5.3 Challenges

  1. Implement operations addActor and deleteActor.

Run these queries to verify they work as expected.

mutation {
  addActor(request:{
    fullName: "Penelope Cruz"
    gender:female
    country:"Spain"
  }){
    id
  }
}
mutation {
  deleteActor(actorId:7){
    id
    fullName
  }
}
  1. Implement operation rateMovie that retrieves a new Input type MovieRateRequest. MovieRateRequest contains the movieID, the user email and the score. The operation will persist data into table movies_rates and will return the Movie.

(Email must be a new scalar type)

input MovieRateRequest {
    movieId:ID!
    email:Email!
    score:Int!
}
type Mutation {
    ...
    rateMovie(request:MovieRateRequest!):Movie!
    ...
}

Run this query to verify it works as expected

mutation {
  rateMovie(request:{
    movieId:1
    email: "thisisme@mail.com"
    score: 8
  }){
    title
  }
}
  1. Modify type Movie and add a new attribute rate whose value is the average score for all the given rates.
mutation {
  rateMovie(request:{
    movieId:1
    email: "thisisme2@mail.com"
    score: 6
  }){
    title
    rate
  }
}

The returned attribute rate must be the average.

  1. Modify operation addMovie. Add a new attribute actorsId (array with the id’s of the actors).

Run this query

mutation {
  addMovie(request:{
    title: "The Irishman"
    year: 2019
    budget:130
    genre: Action
    actorsId: [5,6]
    directorId: 4
  }){
    title
    director{
      fullName
    }
    actors(total:5){
      id
      fullName
    }
  }
}

and the output should be

{
  "data": {
    "addMovie": {
      "title": "The Irishman",
      "director": {
        "fullName": "Martin Scorsese"
      },
      "actors": [
        {
          "id": "5",
          "fullName": "Al Pacino"
        },
        {
          "id": "6",
          "fullName": "Robert de Niro"
        }
      ]
    }
  }
}
  1. Define a new query getMovieRate that retrieves an argument movieId and the output type is MovieRate.

Run this query

query{
  getMovieRate(movieId:1){
    rate
    rates{
      email
      score
    }
  }
}

and the output should look similar to this:

{
  "rate": "7",
  "rates": [
    {
      "email": "john.doe@mail.com",
      "score": 8
    },
    {
      "email": "john.doe@mail.com",
      "score": 6
    }
  ]
}
  1. Create a new subscription listenRates. This operation retrieves an argument movieId and It displays the new rates for the given movieId.

From one tab we run the subscription

subscription{
  listenRates(movieId:1){
    title
    rate
  }
}

and then we add a new rate for movie with identifier 1

mutation{
  rateMovie(request:{
    movieId: 1
    email:"yo@mail2.com"
    score:3
  }){
    director{
      id
      fullName
    }
  }
}

The subscription should print the details for movie with id 1