<img src="https://pursuit.purescript.org/packages/purescript-graphql-client/badge" alt="purescript-graphql-client on Pursuit">
A typesafe graphql client for purescript.
This library will allow you to make graphql queries and type checks the query, arguments and response.
It includes functions for making graphql queries and codegen tools for making sure your GraphQL schema and Purescript schema are in sync.
Here is a complete application using purescript-graphql-client, that makes a graphQL query and logs the result, without using schema codegen.
module Main where
import Prelude
import Data.Argonaut.Decode (class DecodeJson)
import Effect (Effect)
import Effect.Aff (Aff, launchAff_)
import Effect.Class.Console (logShow)
import GraphQL.Client.Args ((=>>))
import GraphQL.Client.Query (query_)
import GraphQL.Client.Types (class GqlQuery)
import Type.Proxy (Proxy(..))
main :: Effect Unit
main =
launchAff_ do
{ widgets } <-
queryGql "Widget names with id 1"
{ widgets: { id: 1 } =>> { name } }
logShow $ map _.name widgets
-- Run gql query
queryGql ::
forall query returns.
GqlQuery Nil' OpQuery Schema query returns =>
DecodeJson returns =>
String -> query -> Aff returns
queryGql = query_ "http://localhost:4892/graphql" (Proxy :: Proxy Schema)
-- Schema
type Schema
= { prop :: String
, widgets :: { id :: Int } -> Array Widget
}
type Widget
= { name :: String
, id :: Int
}
-- Symbols
prop :: Proxy "prop"
prop = Proxy
name :: Proxy "name"
name = Proxy
Either use spago (recommended)
spago install graphql-client
or install with bower
bower install purescript-graphql-client
In order to use this library you will need a Purescript representation of your GraphQL schema.
To get started you can convert your grapqhl schema into a purescript schema, using the codegen tool at https://gql-query-to-purs.herokuapp.com . If you are just testing this library out you can paste your graphql schema on the left, copy the purescript schema from the right and add it to your codebase.
If you are looking for a production solution to schema codegen read the rest of this section. If you are just trying the library out, you can skip to the next section.
It is possible to write the schema yourself but it is easier and safer to use the library's codegen tools.
There is an npm library that is a thin wrapper around this library's schema codegen. First, install this package:
npm i -D purescript-graphql-client
Then add a script to generate your schema on build. Run this script before compiling your purescript project.
const { generateSchema } = require('purescript-graphql-client')
generateSchema({
dir: './src/generated', // Where you want the generated code to go
modulePath: ['Generated', 'Gql'], // The name of the generated module
url: 'http://localhost:4892/graphql' // Your graphql enppdint
})
A full example can be seen in examples/2-codegen
The full options for generateSchema
can be seen in codegen/schema/README.md
You should run this script to build your schema as part of your build, before purescript compilation.
If you wish to generate multiple schemas, use generateSchemas
const { generateSchemas } = require('purescript-graphql-client')
generateSchemas({
dir: './src/generated',
modulePath: ['Generated', 'Gql']
}, [
{
url: 'http://localhost:4892/graphql',
moduleName: 'MySchema' // The name of the module for this single schema
}
])
A full example can be seen in examples/2-codegen
The full options for generateSchemas
can be seen in codegen/schema/README.md
To use purescript-graphql-client in the browser you have a few options for a base client.
You can also create your own base client by making your own data type an instance of QueryClient
. Look in GraphQL.Client.BaseClients.Affjax.Node
for a simple example
To use Affjax you can create a base client using the AffjaxNodeClient
data constructor and
pass it the url of your GraphQL endpoint and any request headers.
To use Apollo you will have to install the Apollo npm module.
npm i -S @apollo/client
you can then create a client using createClient
. eg.
import MySchema (Query, Mutation)
import GraphQL.Client.BaseClients.Apollo (createClient)
import GraphQL.Client.Query (query)
import GraphQL.Client.Types (Client)
import Type.Data.List (Nil')
...
client :: Client _ Nil' Query Mutation Void <- createClient
{ url: "http://localhost:4892/graphql"
, authToken: Nothing
, headers: []
}
query client "my_query_name"
{ things:
{ prop_a: unit
, prop_b: unit
}
}
Look in examples/4-mutation
for a complete example.
Use createSubscriptionClient
if you want to make subscriptions. eg.
import Halogen.Subscription as HS
import MySchema (Query, Subscription, Mutation)
import GraphQL.Client.BaseClients.Apollo (createSubscriptionClient)
import GraphQL.Client.Subscription (subscription)
import GraphQL.Client.Types (Client)
import Type.Data.List (Nil')
...
client :: Client _ Nil' Query Mutation Subscription <-
createSubscriptionClient
{ url: "http://localhost:4892/graphql"
, authToken: Nothing
, headers: []
, websocketUrl: "ws://localhost:4892/subscriptions"
}
let
event = subscription client "get_props"
{ things:
{ prop_a: unit
, prop_b: unit
}
}
cancel <-
HS.subscribe event \e -> do
log "Event recieved"
logShow e
To use this library server-side, you should use the Affjax base client and install xhr2
npm i -S xhr2
You can see an examples of this in examples/1-simple
and e2e/1-affjax
.
You can then write queries and mutations just as you would in the browser.
To view examples of what can be done with this library look at the examples
and e2e
directories.
API documentation can be found at https://pursuit.purescript.org/packages/purescript-graphql-client
Once you are set up and have generated your purescript schema. You can write your queries.
The easiest way to do this is to go to https://gql-query-to-purs.herokuapp.com/query and paste your graphql query on the left. I usually copy the GraphQL query directly from GraphiQL (GraphQL IDE).
You have to the option to make the queries with either unit
s to mark scalar values (leaf nodes) or symbol record puns. The symbol record puns are slightly less verbose and closer to GraphQL syntax but require you import the generated Symbols module.
By default, the library uses decodeJson
from Data.Argonaut.Decode
to decode Json responses
and encodeJson
from Data.Argonaut.Encode
to encode Json. This can be overridden by using the "WithDecoder" versions of functions.
queryWithDecoder
mutationWithDecoder
subscriptionWithDecoder
queryOptsWithDecoder
mutationOptsWithDecoder
subscriptionOptsWithDecoder
With these you can set your own decoder. The library provides a decoder and encoder that works with Hasura. eg.
result <- queryWithDecoder decodeHasura client "query_to_hasura_service"
{ widget:
{ prop1, prop2 }
}
Arguments can be added using the Args
constructor or the =>>
operator. I recommend using the query codegen tool to test this out and see how it works.
As GraphQL arguments may have mixed types, the library provides tools to help handle this.
ArgL
and ArgR
allow you to have different types for different code branches in arguments.
eg.
let condition = true
result <- query client "args_of_differing_types"
{ widget: (if condition then ArgL { x: 1 } else ArgR { y: "something" })
=>>
{ prop1, prop2 }
}
IgnoreArg
can be used to ignore both the label and value on a record.
This is most commonly used with guardArg
to ignore an argument property unless a condition is met.
eg.
let condition = true
result <- query client "only_set_arg_if"
{ widget: { x: guardArg condition 1 }
=>>
{ prop1, prop2 }
}
GraphQL arrays can be written as purescript arrays if they are homogenous, but for mixed type arrays
you can use AndArgs
/andArg
or the +++
/++
operator.
eg.
result <- query client "mixed_args_query"
{ widget:
{ homogenous_array_prop: [1, 2, 3]
, mixed_array_prop: 1 ++ "hello"
, mixed_array_prop2: [1, 2] +++ ["hello", "world"]
}
=>>
{ prop1, prop2 }
}
It is possible to alias properties using the alias operator :
from GraphQL.Client.Alias
.
eg.
import GraphQL.Client.Alias ((:))
import Generated.Symbols (widgets) -- Or wherever your symbols module is
...
query client "my_alias_query"
{ widgets: { id: 1 } =>> { name }
, widgetWithId2: widgets : { id: 2 } =>> { name }
}
Sometimes it is useful to create aliased queries or mutations from a collection of size unknown at compile time.
In a dynamic language you might fold a collection of users to create a graphql query like:
mutation myUpdates {
_1: update_users(where: {id : 1}, _set: { value: 10 }) { affected_rows }
_2: update_users(where: {id : 2}, _set: { value: 15 }) { affected_rows }
_3: update_users(where: {id : 3}, _set: { value: 20 }) { affected_rows }
}
To do this in this library there is there is the Spread
constructor that creates these aliases for you and decodes the response as an array.
eg.
import GraphQL.Client.Alias.Dynamic (Spread(..))
import Generated.Symbols (update_users) -- Or wherever your symbols module is
...
query client "update_multiple_users"
$ Spread update_users
[ { where: { id: 1}, _set: { value: 10 } }
, { where: { id: 2}, _set: { value: 15 } }
, { where: { id: 3}, _set: { value: 20 } }
]
{ affected_rows }
Look alias example in the examples directory for more details.
It is possible to define variables using the Var
contructor
and substitute them using the withVars
function
eg.
import GraphQL.Client.Variable (Var(..))
import GraphQL.Client.Variables (withVars)
...
query client "widget_names_with_id_1"
$ { widgets: { id: Var :: _ "idVar" Int } =>> { name }
}
`withVars`
{ idVar: 1 }
withVars
uses encodeJson
to turn the variables in json. If you
wish to use a custom encoder, use withVarsEncode
.
To provide custom types as variables you will have to make them an instance of VarTypeName
.
This type class specifies their graphql type.
There is a full example in the examples directory.
Only top level directives, that have a query, mutation or subscription location are currently supported.
Please look in the example/12-directives to see an example of this.
If you wish to get the full response, as per the GraphQL Spec use the "FullRes" versions of the query functions
queryFullRes
mutationFullRes
subscriptionFullRes
These will include all errors and extensions in the response, even if a response of the correct type has been returned.
If you wish to get the full response as json use the "Json" versions of the query functions
queryJson
mutationJson
subscriptionJson
These will return the raw json returned by the server inside a newtype GqlResJson
with phanton types for the schema, query and response. These can be useful for creating your own abstractions using that require the unchanged json response.
With apollo you can make type checked cache updates. To see examples of this look at examples/6-watch-query
and examples/7-watch-query-optimistic
. You can also set many options for queries, mutations and subscriptions using , queryOpts
, mutationOpts
, subscriptionOpts
respectively.
To see how these options work, I recommend looking at the Apollo core docs
The options are usually set using record updates or identity
for default options.
eg.
mutationOpts _
{ update = Just update
, fetchPolicy = Just NetworkOnly
}
client
"make_post"
{ addPost: { author, comment } =>> { author: unit }
}
A much more lightweight graphql client. This package does not infer query types and does not support subscriptions or caching but allows writing in graphql syntax and has much less source code. Probably preferable if your query types are not too complex and you do not need subscriptions or caching.
A port of elm-graphql.
Although the names and scope of the 2 packages are very similar they are not connected and there are a few differences: