Skip to content

Overview

GraphQL Objects and Interfaces represent a list of named fields, each of which yield a value of a specific type. KGraphQL inspects defined operations to create type system, but schema creator is able to explicitly declare and customize types. Besides, only member properties are inspected.

type { } type method is entry point to Type DSL. See Extension Properties, Kotlin Properties, Union Properties.

Example

data class Person(val name: String, val age: Int)

KGraphQL.schema {
    type<Person>{
        description = "Human"
    }
}

KProperty1.ignore extension function ignore() makes KGraphQL ignore its receiver property.

Example

data class Person(val name: String, val age: Int)

KGraphQL.schema {
    type<Person>{
        Person::age.ignore()
    }
}

transformation(KProperty1) {}

transformation method allows schema creator to attach data transformation on any kotlin property of type. Like operations, transformation has property resolver which is used to declare data transformation.

Example

data class Person(val name: String, val age: Int)

KGraphQL.schema {
    type<Person>{
        transformation(Person::age) {
            //inMonths is nullable, so client can fetch age property without passing any value to this argument
            //if(null == true) evaluates to false, if(null) is invalid kotlin code
            age: Int , inMonths : Boolean? -> if(inMonths == true) age * 12 else age
        }
    }
}

Kotlin Properties

Kotlin properties are automatically inspected during schema creation. Schema DSL allows ignoring and deprecation of kotlin properties

Example

data class Person(val name: String, val age: Int)

KGraphQL.schema {
    type<Person>{
        property(Person::age){
            ignore = true
        }
        property(Person::name){
            deprecate("Person need no name")
        }
    }
}

Extension Properties

Extension properties allow schema creator to easily attach additional field to any type. It is separately evaluated after main entity is resolved.

property

property method accepts resolver and can be subject of deprecation.

Example

data class Person(val name: String, val age: Int)

KGraphQL.schema {
    type<Person>{
        property<Boolean>("isChild"){
            resolver { person -> (person.age <= 18) }
        }
    }
}

Union Properties

As Kotlin does not support unions, union properties have to be explicitly declared in Schema DSL. Creating union property requires defining resolver and return type. Return Type is reference returned by creation of union type.

Union type resolver is not type checked. Invalid resolver implementation which would return value of type other than members of union type will fail in runtime.

Example

data class UnionMember1(val one : String)

data class UnionMember2(val two : String)

data class Person(val name: String, val age: Int)

KGraphQL.schema {
    type<Person>{
        unionProperty("union"){
            nullable = true // Defaults to false
            returnType = unionType("UnionExample"){
                 type<UnionMember1>()
                 type<UnionMember2>()
            }
            resolver { person -> if (person.age <= 18) UnionMember1("one") else UnionMember2("two") }
        }
    }
}

Data Loaded Properties

This feature is still in experimental state.

One issue that you could easily encounter when doing a GraphQL API is the N+1 Problem. You can read more about this problem and solution in depth here: The GraphQL Dataloader Pattern: Visualized

Imagine having this query:

{
  people(first: 10) {
    name
    friends(first: 5) {
      name
    }
  }
}

Running this against a Database would execute 11 SQL queries: First fetch the 10 people. Then loop through each of them and fetch their first 5 friends one by one.

While what we would like is to fetch the first 10 people in 1 SQL and then fetch the first 5 friends for all those 10 people in the second query. That's what the dataProperty will solve for you.

Setting up

Using DataLoaders requires the DataLoaderPrepared executor:

configure {
    executor = Executor.DataLoaderPrepared
}

example

data class Person(val id: Int, val name: String)
val people = (1..5).map { Person(it, "Name-$it") }
...
configure {
    executor = Executor.DataLoaderPrepared
}
query("people") {
    resolver { -> people }
}
type<Person> {
    // Int - defines the key that will be sent from the [prepare] into [loader]
    // Person? - defines the return type that the [loader] is required to return.
    // the loader is then required to return it in a map format like Map<Int, Person?>
    dataProperty<Int, Person?>("nextPerson") {
        prepare { person, skipAmount: Int -> person.id + skipAmount }
        loader { ids ->
            ids.map {
                ExecutionResult.Success(people[it-1])
            }
        }
    }
}

GraphQL Query example:

{
    people {
        name
        nextPerson(skipAmount: 2) {
          name
        }
    }
}

Returns:

{
  "data": {
    "people": [
      {
        "name": "Name-1",
        "nextPerson": {
          "name": "Name-3"
        }
      },
      {
        "name": "Name-2",
        "nextPerson": {
          "name": "Name-4"
        }
      },
      {
        "name": "Name-3",
        "nextPerson": {
          "name": "Name-5"
        }
      },
      {
        "name": "Name-4",
        "nextPerson": null
      },
      {
        "name": "Name-5",
        "nextPerson": null
      }
    ]
  }
}

Current known issues

This feature can be used in production but does currently have some issues:

  1. The useDefaultPrettyPrint doesn't work.
  2. Order of fields are not guaranteed, to match the order that was requested
  3. Other than that it should work as expected.