Given a GraphQL schema (introspection query response) and a GraphQL query, ppx_graphql
generates three values: (1) a GraphQL query (2) a function to construct the associated query variables, and (3) a function for parsing the GraphQL JSON response into a typed value (object type).
Here's an example of using ppx_graphql
and the generated values (the schema is shown at the top in GraphQL Schema Language):
(*
enum ROLE {
USER
ADMIN
}
type User {
id: ID!
role: ROLE
contacts: [User!]!
}
type Query {
user(id: ID!): User
}
schema {
query: Query
}
*)
let query, kvariables, parse = [%graphql {|
query FindUser($id: ID!) {
user(id: $id) {
id
role
contacts {
id
}
}
}
|}] in
(* ... *)
In this example, the following values are generated:
-
query
(typestring
) is the GraphQL query to be submitted. Currently it's an unmodified version of the string provided to%graphql
, but it will likely be modified in the future, e.g. to inject__typename
for interface disambiguation. -
kvariables
(type(Yojson.Basic.json -> 'a) -> id:string -> unit -> 'a
) is a function to construct the JSON value to submit as query variables (doc). Note that the first argument is a continuation to handle the resulting JSON value -- this makes it easier to write nice clients (see more below). The type is extracted from the query. Required variables appear as labeled arguments, optional variables appear as optional arguments. -
parse
is a function for parsing the JSON response from the server and has the type:Yojson.Basic.json -> <user: <id: string; role: [> `USER | `ADMIN] option; contacts: <id: string> list> >
This type captures the shape of the GraphQL response in a type-safe fashion based on the provided schema. Scalars are converted to their OCaml equivalent (e.g. a GraphQL
String
is an OCamlstring
), nullable types are converted tooption
types, enums to polymorphic variants, lists to list types and GraphQL objects to OCaml objects. Note that this function will likely return aresult
type in the future, as the GraphQL query can fail.
With the above, it's possible to write quite executable queries quite easily:
let executable_query (query, kvariables, parse) =
kvariables (fun variables ->
let response_body = (* construct HTTP body here and submit to GraphQL endpoint *) in
Yojson.Basic.of_string response_body
|> parse
)
let find_user_role = executable_query [%graphql {|
query FindUserRole($id: ID!) {
user(id: $id) {
role
}
}
|}]
Here find_user_role
has the type id:string -> unit -> <user: <role: [`USER | `ADMIN] option> option>
. See github.ml
for a real example using Lwt
and Cohttp
.
[%graphql ...]
expects a file schema.json
to be present in the same directory as the source file. This file should contain an introspection query response.
For use with jbuilder, use the preprocess
- and preprocessor_deps
-stanza:
(executable
(preprocess (pps (ppx_graphql)))
(preprocessor_deps ((file schema.json)))
...
)
When a field of type union is part of your GraphQL query, you must select __typename
on that field, otherwise you will get a runtime error! This limitation is intended to be solved in the future.
Example:
let _ = [%graphql {|
query SearchRepositories($query: String!) {
search(query: $query, type: REPOSITORY, first: 5) {
nodes {
__typename
...on Repository {
nameWithOwner
}
}
}
}
|}]
- No support for input objects
- No support for interfaces
- No support for custom scalar types
- Poor error handling
- Error reporting should be improved
- Path to JSON introspection query result is hardcoded to "schema.json"
- Assumes the query has already been validated