GraphQL
25 Jan 2018notes
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:
- to mark field as Non-Null in schema
- to mark field argument as Non-Null in query
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:
-
with a keyword
field :profile, Types::ProfileType, name: 'Profile'
-
inside the block
field :profile, Types::ProfileType do name 'Profile' end
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
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
- https://github.com/rmosolgo/graphql-ruby/issues/146
- https://github.com/rmosolgo/graphql-ruby/issues/780
- 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
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
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.