'GraphQL' Server in R.
gqlr: A GraphQL Implementation in R
Overview
GraphQL is a data "query language created by Facebook in 2015 for describing the capabilities and requirements of data models for client‐server applications". The GraphQL specification can be read on their hosted GitHub. A cliff notes version is described on GraphQL's GitHub. To learn more about the GraphQL language, I highly recommend GraphQL's website.
This package pulls inspiration from graphql-js and Mathew Mueller's graph.ql. I wanted the full functionality of GraphQL, but I didn't want to force users to write full object definitions when can already be defined with GraphQL syntax.
gqlr
merges R's rapid development speed with the consistent query language of GraphQL.
Installation
# The development version from GitHub:
# install.packages("devtools")
devtools::install_github("schloerke/gqlr")
If you encounter a clear bug, please file a minimal reproducible example on GitHub.
Usage
Hello World
library(magrittr)
library(gqlr)
schema <- "
type Hello {
world: String
}
schema {
query: Hello
}
" %>%
gqlr_schema()
execute_request("{world}", schema, initial_value = list(world = "Hi!"))
# {
# "data": {
# "world": "Hi!"
# }
# }
Star Wars
Star Wars Data
add_human <- function(human_data, id, name, appear, home, friend) {
human <- list(id = id, name = name, appearsIn = appear, friends = friend, homePlanet = home)
# set up a function to be calculated if the field totalCredits is required
human$totalCredits <- function(obj, args, schema) {
length(human$appearsIn)
}
human_data[[id]] <- human
human_data
}
add_droid <- function(droid_data, id, name, appear, pf, friend) {
droid <- list(id = id, name = name, appearsIn = appear, friends = friend, primaryFunction = pf)
# set extra fields manually
droid$totalCredits <- length(droid$appearsIn)
droid_data[[id]] <- droid
droid_data
}
human_data <- list() %>%
add_human("1000", "Luke Skywalker", c(4, 5, 6), "Tatooine", c("1002", "1003", "2000", "2001")) %>%
add_human("1002", "Han Solo", c(4, 5, 6), "Corellia", c("1000", "1003", "2001")) %>%
add_human("1003", "Leia Organa", c(4, 5, 6), "Alderaan", c("1000", "1002", "2000", "2001"))
droid_data <- list() %>%
add_droid("2000", "C-3PO", c(4, 5, 6), "Protocol", c("1000", "1002", "1003", "2001")) %>%
add_droid("2001", "R2-D2", c(4, 5, 6), "Astromech", c("1000", "1002", "1003"))
all_characters <- list() %>% append(human_data) %>% append(droid_data)
all_characters[[1]]
# $id
# [1] "1000"
#
# $name
# [1] "Luke Skywalker"
#
# $appearsIn
# [1] 4 5 6
#
# $friends
# [1] "Tatooine"
#
# $homePlanet
# [1] "1002" "1003" "2000" "2001"
#
# $totalCredits
# function (obj, args, schema)
# {
# length(human$appearsIn)
# }
# <environment: 0x7fadd8ca2038>
Star Wars Schema
"
enum Episode { NEWHOPE, EMPIRE, JEDI }
interface Character {
id: String!
name: String
friends: [Character]
appearsIn: [Episode]
}
type Droid implements Character {
id: String!
name: String
friends: [Character]
appearsIn: [Episode]
primaryFunction: String
}
type Human implements Character {
id: String!
name: String
friends: [Character]
appearsIn: [Episode]
homePlanet: String
}
type Query {
hero(episode: Episode): Character
human(id: String!): Human
droid(id: String!): Droid
}
# the schema type must be provided if a query or mutation is to be executed
schema {
query: Query
}
" %>%
gqlr_schema(
Episode = list(
resolve = function(episode_id, schema) {
switch(as.character(episode_id),
"4" = "NEWHOPE",
"5" = "EMPIRE",
"6" = "JEDI",
"UNKNOWN_EPISODE"
)
}
),
Character = list(
resolve_type = function(id, schema) {
ifelse(id %in% names(droid_data), "Droid", "Human")
}
),
Human = list(
# Add a resolve method for type Human that takes in an id and returns the human data
resolve = function(id, args, schema) {
human_data[[id]]
}
),
Droid = list(
# description for Droid
description = "A mechanical creature in the Star Wars universe.",
# Add a resolve method for type Droid that takes in an id and returns the droid data
resolve = function(id, schema) {
droid_data[[id]]
}
),
Query = function(null, schema) {
list(
# return a function for key 'hero'
# the id will be resolved by the appropriate resolve() method of Droid or Human
hero = function(obj, args, schema) {
episode <- args$episode
if (identical(episode, 5) || identical(episode, "EMPIRE")) {
"1000" # Luke
} else {
"2001" # R2-D2
}
},
# the id will be resolved by the Human resolve() method
human = function(obj, args, schema) {
args$id
},
# the id will be resolved by the Droid resolve() method
droid = function(obj, args, schema) {
args$id
}
)
}
) ->
star_wars_schema
Star Wars Execution
# Use the resolve method to initialize the data
"
{
hero {
id
name
friends {
id
name
}
}
}
" %>%
execute_request(star_wars_schema)
# {
# "data": {
# "hero": {
# "id": "2001",
# "name": "R2-D2",
# "friends": [
# {
# "id": "1000",
# "name": "Luke Skywalker"
# },
# {
# "id": "1002",
# "name": "Han Solo"
# },
# {
# "id": "1003",
# "name": "Leia Organa"
# }
# ]
# }
# }
# }
# Use variables...
"
query FetchSomeIDQuery($someId: String!) {
human(id: $someId) {
name
}
}
" %>%
execute_request(star_wars_schema, variables = list(someId = "1000"))
# {
# "data": {
# "human": {
# "name": "Luke Skywalker"
# }
# }
# }
# Introspection
"
query IntrospectionTypeQuery {
__type(name: \"Droid\") {
kind
name
fields {
name
}
}
}
" %>%
execute_request(star_wars_schema)
# {
# "data": {
# "__type": {
# "kind": "OBJECT",
# "name": "Droid",
# "fields": [
# {
# "name": "id"
# },
# {
# "name": "name"
# },
# {
# "name": "friends"
# },
# {
# "name": "appearsIn"
# },
# {
# "name": "primaryFunction"
# },
# {
# "name": "__typename"
# }
# ]
# }
# }
# }
Star Wars Server
# R
gqlr:::server(star_wars_schema, log = TRUE) # forgot to export
Explore with curl
# GET R2-D2 and his friends' names
curl '127.0.0.1:8000/graphql?query=%7Bhero%7Bname%7D%7D&pretty=TRUE'
# {
# "data": {
# "hero": {
# "name": "R2-D2"
# }
# }
# }
# POST for R2-D2 and his friends' names (no need to url escape the query)
curl --data '{"query":"{hero{name}}"}' '127.0.0.1:8000/graphql' # defaults to parse as JSON
# {"data":{"hero":{"name":"R2-D2"}}}
curl --data '{"query":"{hero{name}}"}' '127.0.0.1:8000/graphql' --header "Content-Type:application/json"
# {"data":{"hero":{"name":"R2-D2"}}}
curl --data '{hero{name}}' '127.0.0.1:8000/graphql' --header "Content-Type:application/graphql"
# {"data":{"hero":{"name":"R2-D2"}}}
# GET Schema definition
curl '127.0.0.1:8000/'
# enum Episode {
# NEWHOPE
# EMPIRE
# JEDI
# }
#
# type Droid implements Character {
# id: String!
# name: String
# friends: [Character]
# appearsIn: [Episode]
# primaryFunction: String
# }
#
# type Human implements Character {
# id: String!
# name: String
# friends: [Character]
# appearsIn: [Episode]
# homePlanet: String
# }
#
# type Query {
# hero(episode: Episode): Character
# human(id: String!): Human
# droid(id: String!): Droid
# }
#
# interface Character {
# id: String!
# name: String
# friends: [Character]
# appearsIn: [Episode]
# }
#
# schema {
# query: Query
# }
Presentations
WOMBAT2016 in Melbourne, Australia; Feb 2016.