Resolver
A resolver is a function which is in charge of returning a field of the Graph.
Basic resolver
Let's take this GraphQL schema as example
type Query {
movies: [Movie!]!
}
type Mutation {
addMovie(title: String!, directorName: String!): Movie!
}
type Movie {
title: String!
director: Director!
}
type Director {
name: String!
movies: [Movie!]
}
It will allow users to fetch books and/or add a book.
You'll have to register a first resolver function which will resolve the field Query.movies
.
To do so, register your resolver like this:
- Without codegen
- With codegen
data class Director(val name: String)
data class Movie(val title: String, val director: Director)
val movies = mutableListOf<Movie>()
fun main() = arianeServer {
resolvers {
Query {
resolve("movies") {
movies // This will return all the movies
}
}
}
}.launch()
data class Director(val name: String)
data class Movie(val title: String, val director: Director)
val movies = mutableListOf<Movie>()
fun main() = arianeServer {
resolvers {
Query {
movies {
movies // This will return all the movies
}
}
}
}.launch()
Arguments
Now, let's look at the field Mutation.addMovie
:
type Mutation {
addMovie(title: String!, directorName: String!): Movie!
}
It's receiving parameters (title
and director
).
To handle them, you can use one field of the map passed to the resolver's lambda.
val movies = mutableListOf<Movie>()
fun main() = arianeServer {
resolvers {
Mutation {
resolve("addMovie") {
val title = it["title"]
val directorName = it["director"]
val movie = Movie(title, Director(directorName))
movies.add(movie)
movie
}
}
}
}.launch()
Arguments typing
You can also map the argument to a defined type like this:
data class AddMovieArgument(val title: String, val director: String)
val movies = mutableListOf<Movie>()
fun main() = arianeServer {
resolvers {
Mutation {
resolve<AddMovieArgument>("addMovie") { argument ->
val title = argument.title
val directorName = argument.director
val movie = Movie(title, Director(directorName))
movies.add(movie)
movie
}
}
}
}.launch()
The codegen plugin generates everything for you:
val movies = mutableListOf<Movie>()
fun main() = arianeServer {
resolvers {
Mutation {
addMovie { (title, director) ->
val movie = Movie(title, Director(director))
movies.add(movie)
movie
}
}
}
}.launch()
Nesting fields
If you look at the type Director
:
type Director {
name: String!
movies: [Movie!]
}
It contains the field movies
, but we never returned it. So, what happen if a client execute this query?
query GetMovies {
movies {
title
director {
name
movies {
title
}
}
}
}
Ariane won't be able to resolve the field Director.movies
.
To do so, you can add a resolver function like this:
- Without codegen
- With codegen
val movies = mutableListOf<Movie>()
fun main() = arianeServer {
resolvers {
type<Director>("Director") {
resolve("movies") {
movies.filter { it.director.name == source.name }
}
}
}
}.launch()
val movies = mutableListOf<Movie>()
fun main() = arianeServer {
resolvers {
Director {
movies {
movies.filter { it.director.name == source.name }
}
}
}
}.launch()
Object oriented Resolver
If you prefer to use resolver as a Class, you can create one and register it like this:
data class AddMovieArgument(val title: String, val director: String)
class AddMovieResolver : Resolver<GraphQLTypes.Mutation, GetMovieArguments> {
override suspend fun resolve(arguments: AddMovieArgument, source: GraphQLTypes.Query, context: GraphQLContext, info: Info): Any? {
// Return the movies here
}
}
The generic type refers to the source
parameter type, and its argument
type.See (resolver parameters)
Then register it like this:
val addMovieResolver = AddMovieResolver()
fun main() = arianeServer {
resolvers {
Mutation {
resolve("addMovie", addMovieResolver)
}
}
}.launch()
With the codegen plugin, it's even simpler, it'll generate the interface so you can simply implements it.
The name of the generated interface will be ${SourceName}${FieldName}Resolver
. Example:
//generated
interface MutationAddMovieResolver: Resolver<GraphQLTypes.Mutation, AddMovieArgument>
So you just have to implement the generated interface
class GetMoviesResolver : MutationAddMovieResolver {
override suspend fun resolve(arguments: AddMovieArgument, source: GraphQLTypes.Query, context: GraphQLContext, info: Info): Any? {
// Return the movies here
}
}
And register it like this:
val addMovieResolver = AddMovieResolver()
fun main() = arianeServer {
resolvers {
Mutation {
addMovie(addMovieResolver)
}
}
}.launch()
Organize resolvers
As your project grows, you may want to split resolvers in different files.
It's possible to merge them into a single ariane configuration like this:
- main.kt
- movies/resolvers.kt
- directors/resolvers.kt
fun main() = arianeServer {
resolvers {
addResolvers(movieResolvers)
addResolvers(directorResolvers)
}
}.launch()
val movieResolvers = resolvers {
Query {
movies {
(...)
}
}
Mutation {
addMovie {
(...)
}
}
}
val directorResolvers = resolvers {
Query {
directors {
(...)
}
}
Movie {
director {
(...)
}
}
}