A simple GraphQL client on top of Sangria, Akka HTTP and Circe.
Add the following dependency:
resolvers += Resolver.bintrayRepo("jarlakxen", "maven")
"com.github.jarlakxen" %% "drunk" % "2.5.0"
Then, import:
import com.github.jarlakxen.drunk._
import io.circe._, io.circe.generic.semiauto._
import sangria.macros._
There are three ways to create a GraphQLClient
:
- As Akka Https flow connection
import akka.http.scaladsl.model.Uri
val uri: Uri = Uri(s"https://$host:$port/api/graphql")
val http: HttpExt = Http()
val flow: Flow[HttpRequest, HttpResponse, Future[OutgoingConnection]] = http.outgoingConnectionHttps(uri.authority.host.address(), uri.effectivePort)
val client = GraphQLClient(uri, flow, clientOptions = ClientOptions.Default, headers = Nil)
- As Akka Http flow connection
import akka.http.scaladsl.model.Uri
val uri: Uri = Uri(s"http://$host:$port/api/graphql")
val http: HttpExt = Http()
val flow: Flow[HttpRequest, HttpResponse, Future[OutgoingConnection]] = http.outgoingConnection(uri.authority.host.address(), uri.effectivePort)
val client = GraphQLClient(uri, flow, clientOptions = ClientOptions.Default, headers = Nil)
- As Akka Http single request
val client = GraphQLClient(s"http://$host:$port/api/graphql")
Then, query:
val query =
graphql"""
query HeroAndFriends {
hero {
id
name
friends {
name
}
appearsIn
}
}
"""
val cursor: GraphQLCursor = client.query[HeroQuery](query)
val data: Future[GraphQLResponse[HeroQuery]] = cursor.result
type HerosQuery = Map[String, List[Hero]]
case class Pagination(offset: Int, size: Int)
val query =
graphql"""
query Heros($offset: Int, $size: Int) {
heros(offset: $offset, size: $size) {
id
name
friends {
name
}
appearsIn
}
}
"""
val page1: GraphQLCursor[HerosQuery, Pagination] =
client.query(query, Pagination(0, 10))
val page2: GraphQLCursor[HerosQuery, Pagination] =
page1.fetchMore(lastPage => lastPage.copy(offset = lastPage.offset + lastPage.size ) )
import com.github.jarlakxen.drunk._
import io.circe._, io.circe.generic.semiauto._
import sangria.macros._
case class User(id: String, name: String)
val client = GraphQLClient(s"http://$host:$port/api/graphql")
val mutation =
graphql"""
mutation($user1: String!, $user2: String!) {
user1: newUser(name: $user1) {
id
name
}
user2: newUser(name: $user2) {
id
name
}
}
"""
val result: Future[GraphQLResponse[Map[String, User]]] =
client.query(mutation, Map("user1" -> "123", "user2" -> "456"))
import com.github.jarlakxen.drunk._
import sangria.introspection.IntrospectionSchema
val client = GraphQLClient(s"http://$host:$port/api/graphql")
val result: Future[GraphQLResponse[IntrospectionSchema]] = client.schema
It's very common in GraphQL to have response with polymorphic objects in the responses. One way to discriminate the type of object is to check the __typename
field. For that purpose there an special derive decoder in com.github.jarlakxen.drunk.circe._
:
import com.github.jarlakxen.drunk.circe._
import io.circe._, io.circe.generic.semiauto._
trait Character {
def id: String
def name: Option[String]
def friends: List[String]
def appearsIn: List[Episode.Value]
}
case class Human(
id: String,
name: Option[String],
friends: List[String],
appearsIn: List[Episode.Value],
homePlanet: Option[String]) extends Character
case class Droid(
id: String,
name: Option[String],
friends: List[String],
appearsIn: List[Episode.Value],
primaryFunction: Option[String]) extends Character
implicit val humanDecoder: Decoder[Human] = deriveDecoder
implicit val droidDecoder: Decoder[Droid] = deriveDecoder
implicit val characterDecoder: Decoder[Character] = deriveByTypenameDecoder(
"Human".decodeAs[Human], // for __typename: 'Human' is going to use humanDecoder
"Droid".decodeAs[Droid] // for __typename: 'Droid' is going to use droidDecoder
)
This code can be use to parse a response like:
{
"__typename": "Droid"
"name": "R2D2"
....
}
Take into account the client automatically adds the '__typename' field to every selector, so it's not required to be added in the queries.
There are several extension for GraphQL, this client supports:
To get the information of the extensions you can:
val cursor: GraphQLCursor = client.query[HeroQuery](query)
val extensions: Future[GraphQLExtensions] = cursor.extensions
val metrics: Future[Option[GraphQLMetricsExtension]] = extensions.map(_.metrics)
val cacheControl: Future[Option[GraphQLCacheControlExtension]] = extensions.map(_.cacheControl)
If you have a question, or hit a problem, feel free to ask in the issues!
Or, if you encounter a bug, something is unclear in the code or documentation, don’t hesitate and open an issue.