GraphQL


notes

http://graphql.org/learn/queries/

GraphQL queries look the same for both single items or lists of items, however we know which one to expect based on what is indicated in the schema.

The operation type is either query, mutation, or subscription and describes what type of operation you’re intending to do.

The operation name is a meaningful and explicit name for your operation.

http://graphql.org/learn/schema/

GraphQL type language can be any language (say, Ruby):

GraphQL services can be written in any language. Since we can’t rely on a specific programming language syntax, like JavaScript, to talk about GraphQL schemas, we’ll define our own simple language. We’ll use the “GraphQL schema language” - it’s similar to the query language, and allows us to talk about GraphQL schemas in a language-agnostic way.

Object types, scalars, and enums are the only kinds of types you can define in GraphQL.

GraphQL comes with a set of default scalar types out of the box:

Int: A signed 32‐bit integer. Float: A signed double-precision floating-point value. String: A UTF‐8 character sequence. Boolean: true or false. ID: The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human‐readable.

Non-Null type modifier (!) is used after type name:

input types look exactly the same as regular object types, but with the keyword input instead of type

http://graphql.org/learn/execution/

Each field on each type is backed by a function called the resolver which is provided by the GraphQL server developer. When a field is executed, the corresponding resolver is called to produce the next value.

If a field produces a scalar value like a string or number, then the execution completes. However if a field produces an object value then the query will contain another selection of fields which apply to that object. This continues until scalar values are reached. GraphQL queries always end at scalar values.

field options

field options can be specified:

name field option

field name is how field is referred to in query:

{
  profile {
    id
  }
}

it can’t contain spaces, must be human-readable and unique within schema:

field :profile, Types::ProfileType # name -> `profile`
field :profile, Types::ProfileType, name: 'Profile' # name -> `Profile`
field :profile, Types::ProfileType do # name -> `Profile`
  name 'Profile'
end

type options

name type option

type name is how type is referred to in query - say, in fragments:

fragment profileFields on Profile {
  id
  name
}

it can’t contain spaces, must be human-readable and unique within schema:

Types::ProfileType = GraphQL::ObjectType.define do
  name 'Profile'

  field :id, !types.Int
  field :name, !types.String
end

input object type arguments

non-nullable arguments can be either null or omitted at all:

# in dry-validation parlance it's equivalent to:
# optional(:field).maybe(:str?)
argument :field, types.String

tips

(how to) use fragments with apollo-fetch client

  1. https://github.com/apollographql/graphql-tag/issues/84
const profileFields = `
  fragment profileFields on Profile {
    id
    user_id
    name
  }
`;

const allProfiles = (token) => {
  const query = `
    query allProfiles {
      profiles {
        ...profileFields
      }
    }
    ${profileFields}
  `;

  return run({query}, token);
};

(how to) define JSON type

  1. https://github.com/rmosolgo/graphql-ruby/issues/146
  2. https://github.com/rmosolgo/graphql-ruby/issues/780
  3. http://www.rubydoc.info/gems/graphql/1.7.9/GraphQL/ScalarType
Types::JSONType = GraphQL::ScalarType.define do
  name 'JSONType'

  # value is ActionController::Parameters object!
  # `value.to_unsafe_h` converts it to simple hash
  coerce_input ->(value, _ctx) { value.to_unsafe_h }
end

don’t make empty sub-selections for object types

  1. http://graphql.org/learn/queries/#fields

this was allowed in graphql gem (1.7.9) but is no longer allowed in graphql gem (1.7.14).

  mutation AddComment($userId: Int!, $comment: String)  {
-   addComment(userId: $gamerId, comment: $comment) {
-   }
+   addComment(userId: $gamerId, comment: $comment) {
+     id
+   }
  }

don’t make selections for scalar types

obviously this would result in error even though it was allowed to make an empty sub-selection of fields in graphql gem (1.7.9) for the field that returns a scalar type:

// addComment mutation field returns Boolean type (a scalar)
const query = `
  mutation AddComment($userId: Int!, $comment: String)  {
    addComment(userId: $gamerId, comment: $comment) {
    }
  }
`;

troubleshooting

[Rails] query is duplicated in params

for some reason query is duplicated in params:

pry> params.to_unsafe_h
{
  "query" => "mutation UpdateProfile($profile: ProfileInput!) ...",
  "variables" => {
    "profile" => {
      "name" => "John Doe",
    }
  },
  "controller" => "api/graphql",
  "action" => "execute",
  "token" => "some_token",
  "graphql" => {
    "query" => "mutation UpdateProfile($profile: ProfileInput!) ...",
    "variables" => {
      "profile" => {
        "name" => "John Doe",
      }
    }
  }
}
pry> params[:query] == params[:graphql][:query]
true

query is not duplicated in network request sent from client.

AppSignal event timeline for GraphQL request (maybe it’s not relevant):

process_action.action_controller
    ...
    execute.graphql × 2

solution

  1. https://github.com/rmosolgo/graphql-ruby/issues/1435

only one query (params[:query]) is passed to MySchema.execute eventually and the second query (params[:graphql][:query]) is not used at all:

result = MySchema.execute(
  params[:query],
  variables: ensure_hash(params[:variables]),
  context: { current_user: current_user },
  operation_name: params[:operationName]
)

resolvers for root query fields are also called only once.

so graphql-ruby gem must be working correctly and query duplication can be safely ignored.

variables are not passed from JS client

it looks like apollo-fetch GraphQL client removes variables which have undefined value - so set them to null explicitly (say, using default function parameters) if they not are specified by invoking code:

const actualGamesForPeriod = (token, startDate = null, endDate = null) => {
  const query = `
    query ActualGamesForPeriod($startDate: String, $endDate: String) {
      actualGamesForPeriod(startDate: $startDate, endDate: $endDate) {
      }
    }
  `;
  const variables = {startDate, endDate};

  return apolloFetch(token)({query, variables});
};

if variables are not passed by GraphQL client, prepare lambdas for corresponding arguments on server side are not even called:

field :actualGamesForPeriod, !types[!Types::GameType] do
  argument :startDate, types.String, prepare: ->(start_date, _ctx) {
    # this lambda is never called if startDate argument is not specified
  }
  # ...

Parse error on “}” (RCURLY) at [4, 7]

Ruby graphql gem (1.7.14) response:

Parse error on "}" (RCURLY) at [4, 7]

solution

don’t use empty sub-selection of fields for the field (addComment mutation field here) that returns a scalar (see the tips above):

  mutation AddComment($userId: Int!, $comment: String)  {
-   addComment(userId: $gamerId, comment: $comment) {
-   }
+   addComment(userId: $gamerId, comment: $comment)
  }

you’ll have to downgrade graphql gem to 1.7.9 in order to support published RN application versions - upgrade only when these versions are no longer used.