This repository is a comprehensive collection of notes on Java and Go programming languages, designed for both beginners and experienced learners. It offers a variety of resources including code snippets, tips, and tricks to help users enhance their knowledge of these languages.
Important: GraphQL responses have the same shape as the results, so everything is always as expected.
Queries can be for subfields or even objects.
Comments in graphql start with #, such as:
{
hero {
name
# Queries can have comments!
}
}
Every field can get it’s own set of arguments, event scalars, useful for implementing data transformations.
Fields are passed to objects like (field_name: field_value). For example:
{
human(id: "1000") {
name
height(unit: FOOT)
}
}
GraphQL has a default set of types, but you can define your own types to be queried.
Aliases are useful to make different queries for the same type. An alias is defined like alias: collection(...fields), for example:
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
# Gives
{
"data": {
"empireHero": {
"name": "Luke Skywalker"
},
"jediHero": {
"name": "R2-D2"
}
}
}
Fragments are reusable units usable in queries. Fragments contain a set of fields and you can include them when you need to like ...fragment_name, for example:
{
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
appearsIn
friends {
name
}
}
Fragments can use variables declared in the query (or mutation) too, for example:
query HeroComparison($first: Int = 3) {
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
friendsConnection(first: $first) {
totalCount
edges {
node {
name
}
}
}
}
Queries are defined with the keyword query, and can be anonymous or have a name. Named queries are helpful when debugging queries.
Named query:
query HeroNameAndFriends {
hero {
name
friends {
name
}
}
}
Anonymous query:
query {
hero {
name
friends {
name
}
}
}
When working with variables, there’s no need to manipulate the query string, GraphQL has variables you can work with.
To start using variables:
$variableName$variableName as one of the variables accepted by the query (Variable Definition)variableName: value in the sepparate, transport-specific usually JSON# QUERY
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}
# TRANSPORT JSON
{
"episode": "JEDI"
}
NEVER do string interpolation for the queries
Variable definition is the part ($episode: Episode) from the previous query. It has the variable name with $ and the type (in that case Episode).
Optional variables are all unless they have a ! next to the type. If the variable requires a non null argument, the variable needs to be required as well.
Default variables can be declared by adding = value to the variable definition, such as ($episode: Episode = JEDI). You can call the query without the param.
Directives is attached to a field or fragment inclussion and affects how the query executes and which parts execute.
There are 2 main directives:
@include(if: Boolean) –> Include the field only if the if condition is true@skip(if: Boolean) –> Skip the field only if the if condition is trueGraphQL servers can add custom directives, but the previous 2 have to always be supported.
It is a convention that in order to modify values, a Mutation should be used. Mutations also return an object type, so you can ask for fields in that object.
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
# Data returned from new object create
stars
commentary
}
}
# VARIABLES
{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "This is a great movie! BLABLA"
}
}
Query fields run in parallel, but mutations run in series.
GraphQL has support for interfaces and unions. If the query returns an union or interface type, you can use inline fragments to access the concrete underlying type, for example:
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
# Character can be either Human or Droid
# Character is an interface
... on Droid {
primaryFunction
}
... on Human {
height
}
}
}
# VARIABLES
{
"ep": "EMPIRE"
}
There are some situations where you don’t know exactly what you might get, in those cases you can query for __typename which contains the type of the object being returned. In that scenario you can use inline fragments to determine what to return. For example:
{
search(text: "an") {
__typename
... on Human {
name
}
... on Droid {
name
}
... on Starship {
name
}
}
}
The search returns a union that can be any of multiple types. There are multiple meta fields for each object.
Every GraphQL service defines which types you can query for.
This is the most basic components of a GraphQL schema. Objects can be represented like:
# Character is a GraphQL Object Type
type Character {
# Fields from Character
name: String! # Built-in scalar types, non-nullable because of !
appearsIn: [Episode!]! # Array of episode objects, it will allways be an array, empty or not
}
Scalar types –> Resolve to a single scalar object, can’t have sub-selections.
Fields can have 0 or more arguments, for example:
type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}
All arguments are named can be required or optional. If optional, it can have a default value.
The query and mutation types are special schema types. All services will have a query but may not have a mutation type.
Those definitions define which fields you can query for on each query or mutation.
At some point the types must resolve to something concrete, so scalar types are the leaves of the queries.
The scalar types defined in GraphQL are:
Int –> Signed 32-bit integerFloat –> Signed double-precission floating-point valueString –> UTF-8 charecter sequenceBoolean –> true or falseID –> Represents a unique identifier, often to fetch and object or as the key of the cache. ID is serialized the same way as a String, but specifying ID implies it’s not human readable.GraphQL service implementations can define custom scalar types, but it’s up to the service implementation to define how the type should be serialized, deserialized, and validated.
enum types are special types restricted to a set of scalar values. It can allow for validation of the arguments and to better communicate errors.
An enum can be defined like:
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
Fields can be declared as non-null by appending a ! to the name. If the field receives a null value, it will trigger a GraphQL execution error.
Lists work in a similar way, it’s declared that the field will return an array of the set type by surrounding it with [].
These modifiers can be combined:
myField: [String!]
myField: null // valid
myField: [] // valid
myField: ['a', 'b'] // valid
myField: ['a', null, 'b'] // error
myField: [String]!
myField: null // error
myField: [] // valid
myField: ['a', 'b'] // valid
myField: ['a', null, 'b'] // valid
These types can be nested in any number without limits.
Interfaces are abstract types that include a set of fields that a type must include to implement the interface. For example
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}
If you are querying for an Interface type, in order to access parameters on the underlying type you need to use inline fragments (... on Type {...}).
Similar to interfaces, but you don’t specify common fields. For example:
union SearchResult = Human | Droid | Starship
The underlying types have to be concrete types, they cannot be other unions or interfaces.
In case you need to access the values of the underlying types, inline fragments become useful again here:
{
search(text: "an") {
__typename
... on Character {
name
}
... on Human {
height
}
... on Droid {
primaryFunction
}
... on Starship {
name
length
}
}
}
In the case of mutations, it’s possible that you might need to pass the whole object. In that case, it differences by using the keyword input, such as:
input ReviewInput {
stars: Int!
commentary: String
}
Fields can make reference to other input types but not output types, you cannot mix that. Input type fields cannot have arguments too.
Some common validations (more validation info here):
Fragments cannot call themselves
{
hero {
...NameAndAppearancesAndFriends
}
}
fragment NameAndAppearancesAndFriends on Character {
name
appearsIn
friends {
...NameAndAppearancesAndFriends
}
}
Fields for a type must exist, cannot query non-existent fields on types:
# INVALID: favoriteSpaceship does not exist on Character
{
hero {
favoriteSpaceship
}
}
Cannot query just for an object, need to specify the fields:
# INVALID: hero is not a scalar, so fields are needed
{
hero
}
Cannot query for fields inside a scalar:
# INVALID: name is a scalar, so fields are not permitted
{
hero {
name {
firstCharacterOfName
}
}
}
If querying for an interface, you cannot include fields of the underlying type without an inline fragment:
# INVALID: primaryFunction does not exist on Character
{
hero {
name
primaryFunction
}
}