https://spec.graphql.org/October2021/
GraphQL - query language, used to build client applications, describe data requirements. Defines capabilites - does not do computation.
- query language - a language that describes a query?
- graphql service - something that can respond to graphql requests
- documents - may contain operations, describes a complete file or request string operated on by a GraphQL service or client. Can contain multiple definitions, either executable OR representative of a GraphQL type system. If a document contains one operation, that operation may be unnamed. If that single operation is a query without variables or directives, you can omit the
query
keyword and the operation name. - operation - query, mutation, or subscription
- fragments - a unit of composition allowing for reuse
- syntatic grammar - higher level ruleset for how a language works
- lexical grammar - lower level ruleset for how a language works
- SourceCharacter - tab, end of line, carriage return, space
- Token lexical grammar - interesting everyday text - letters, numbers, punctionation, etc
- Selection set - The set of information that an operation opts to receive. Only this set of information is returned as the result of an operation.
- Resolve/Resolver - A function that takes a parent object, a context, and a field name and returns the value for the field on that parent object.
- Request error - An error produced by the GraphQL system before the request is attempted to be executed
- Field error - An error produced by the GraphQL system during the resolution or coercion of a particular field
- starts from a frontend perspective
- hierarchical - in the same way that react is
- strongly typed
- client focused - only return what the client wants
- you can inspect the type system represented/exposed by graphql
Comments are signified with #
Commas are ignored? Used as list delimiters.
- query – a read-only fetch.
- mutation – a write followed by a fetch. Performs side effects on the underlying data system.
- subscription – a long-lived request that fetches data in response to source events.
You can do a shorthand notation if you are representing a single query in a document and that query does not take variables.
Operations are made up of a name and a selection set.
mutation {
likeStory(storyID: 12345) {
story {
likeCount
}
}
}
name is likeStory
and the selection set is all the stuff in the curly braces below the name.
A selection set can contain a field, a fragment spread (e.g. ...FragmentName
), or an inline fragment.
A field describes one discrete piece of information available to request within a selection set. Can contain a selection set, allowing you to nest requests recursively.
When specifying an operation, you must specify selection sets down to scalar values - you can't just select a field that represents e.g. a graphql object that contains other fields.
Fields can take arguments specified like: field(argName: value, argName2: value)
. The order doesn't matter.
Fields can be aliased so that they have different names in the resulting data that is returned:
{
user(id: 4) {
id
name
smallPic: profilePic(size: 64)
bigPic: profilePic(size: 1024)
}
}
Fragments define units of reuse within graphql.
You define them like so:
fragment UserInfo on User {
id
name
}
You use them with the spread (...
) operator like so:
query GetUsers {
users(first: 5) {
photo
...UserInfo
}
}
Fragments must specify the type they apply to.
If you request data that could be many types, the fragment will only operate on the type it is specified to. You specifiy a fragment type with the on
keyword.
You can specify a fragment inline. It's basically the same thing as using the spread operator, except you don't write the fragment name.
- variable
- int
- float
- boolean
- string
- null
- enum
- list
- object
You can declare variables on a operation. If a fragment uses a variable, the root note that transitively includes the fragment must make sure to declare the variable.
Directives are special tags to tell the server to do something interesting. You specify them using @
.
Seems dangerous. If you're using them you better know what you're doing.
- describes the capabilities of a GraphQL service
- determines if a request operation is valid
- guarantees the type of response results
- describes input types
Descriptions represent documentation. Documentation is a first-class concept in GraphQL.
Describes the capabilities of a GraphQL service. Defined in terms of types it supports as well as root operation types: query, mutation, subscription. All types in the schema must have unique names.
- query - required - defines what fields are available at the top level of a GraphQL query operation.
- mutation - optional
- subscription - optional
All defined as "Object" types.
- scalar - represent leaf nodes in the GraphQL hierarchy. Usually are things like Int, Float, String, Boolean, ID, though you can define your own.
- object - represent intermediate nodes as a list of named fields and their associated values
- interface - just like an interface in any other normal type system. Allows you to give a name to a bundle of fields.
- union - give a name to a list of types joined by OR
- enum - represent leaf nodes in the GraphQL hierarchy that describe a set of possible values.
- input object - types the define inputs. Sorta look like Objects. Can only contain scalars, enums, or input objects.
- list - represents many of a particular type
- non-null - denotes that the type it wraps will never be null
- scalar, enum, input object can only be used as inputs
- object, interface, union can only be used as outputs
- lists, non-null can be used as both
Can define custom scalars like URL
or UUID
or anything else.
Represent a list of named fields, each of which yield a value of a specific type. Essentially an ordered map.
If you are querying an object, you must select at least one field within that object.
Object fields may take arguments to specify their output because they are conceptually just functions.
You can mark fields as depreceated.
All types in GraphQL are nullable by default.
A trailing exclamation mark is used to denote non-null types like so: name: String!
Annotations which tell e.g. a validator, executor, or client tool to evaluate something differently.
- @skip - takes a "if" boolean - skip the line if the arg is true
- @inlucde - the inverse of @skip it seems. Both are essentially "if" statements
- @deprecated - indicates deprecated sections
- @specifiedBy - provides a place to put a URL that defines a scalar specification
Can define custom directives. Seems like a very advanced use-case.
Introspection allows queries to find out about the schema of a GraphQL service. So, for example, you can ask "What's the type of this object and what fields does it have?"
Names provided by GraphQL start with __
two underscores. There must not be any other names that start with __
two underscores.
You can query any object's typename by asking for the __typename
field. It exists on any object except the root field of a subscription operation.
All types have the ability to return a description
field that is a string. The string can contain markdown following the CommonMark specification.
Fields can be deprecated with fields isDeprecated
and deprecationReason
.
- scalar - int, string, boolean, custom scalars
- object - sets of fields
- interface - declares a common set of fields. Any implementing type must define all those fields
- union - a thing representing one of several possible types
- enum - defines a set of values
- input_object - defines a set of named input values
- list - sequence of values
- non_null - a type modifier indicating a type can not be null
A GraphQL service should validate a GraphQL request before executing it. It should not execute the request if it is invalid.
- Documents
- A GraphQL service does not/can not take into account types that are defined client-side.
- Operations
- Must have a unique name in the context of the document
- Can be defined without a name as long as there's only one operation in the document
- Subscription operations must have only one root field
- Subscription operations must not define their root field as
__typename
- Fields
- Object, interface, and union types must select one or more fields
- Fields must exist on the type they're being used in
- Fields can not be selected on a union - they must be selected indirectly via a fragment on that union
- Selecting the same field with the same argument(s) more than once is valid and will be merged by
MergeSelectionSets
andCollectFields
- You aren't able to select fields on scalars or enums
- Arguments
- Must only reference defined arguments, must not have multiple arguments with the same name
- Fragments
- Each fragment's name must be unique within a document
- Fragments can only be declared on unions, interfaces, and objects. They can not be declared on leaf nodes: scalars or enums.
- If a fragment is defined within a document, it must be used
- Fragment spreads must not form cycles
- Fragments are declared on a particular type and will only ever be valid for use on that type
- Values
- Literal values must be of the correct type
- Value names are unique
- Directives
- Directives can only be used where they are supported
- Variables
- Must have unique names
- Variables must be of type Input
- Only those variables that are defined can be used. Variables that are used must be defined. There can be no extra variable definitions.
GraphQL generates a response by executing stuff.
A GraphQL request contains:
- the schema to use (usually provided by the GraphQL service)
- a document that contains an operation
- (optional) the name of the operation in the document to execute
- (optional) values for variables in the operation
- ?an initial value corresponding to the root type being executed?
- Get the operation
- Get the variables used in the operation
- Validate the request
- Coerce input variables
- Execute the operation
Each operation to execute begins with a root object: query, mutation, or subscription.
Query fields may be executed in parallel. Mutation fields mut be executed serially.
Subscription operations return an event stream (aka Response Stream). Each event is the result of executing the oparation for each new event in an underlying "Source Stream"
Pub-sub model. Transport mechanisms, serialization, etc are all implementation details of the system and not described in the spec.
Publish -> Source Stream Subscribe -> Event Stream / Response Stream
To execute a selection set:
- Collect all the fields for a selection set on an object into a map from field names to (sub)selection sets
- For every field in that map, resolve/execute the field and produce a value 2a. Recursively execute the selection set for the field
- Ensure the value produced is of the correc type for the field
If a field marked as non-null produces an error, then the error will propagate up to the parent object recursively until there is an object that can be null.
Mutations must be run in serial. Everything else can be run in parallel.
- Create an entry in the response map where the key is the field name (or alias)
- Coerce argument values
- Resolve field value
- recursively execute innner selection set OR coerce a scalar value and add it to the response
If two selection sets are defined for a field of the same name, the field will be resolved, then the selection sets will be merged, and finally the merged selection set will be executed.
Describes:
- a successful operation
- any errors encountered
- might contain a partial response along with errors encountered
Is a map:
{
errors: [{}, ...], (only present if there are errors)
data: {}, (only present if the request executed successfully, might be null [which is a valid value])
extensions: {
(optional, intended for developers to extend protocol)
}
}
Contains the successful response. The output will be an object of type query root if the operation was a query. The output will be an object of type mutation root if the operation was a mutation.
Request error - error parsing the request before execution. Probably the fault of the requesting client. Think malformed request. Field error - error resolving a particular field. Probably the result of the service. Think record not found execption.
It's a map.
{
message: "String description of th error intended for the developer",
locations: [{ line: , column: }, ...],
path: ["field", "subfield", ...], (optional, describes the path of the response field that caused the error)
}