graphql-client
A client for Haskell applications to query GraphQL APIs. This package provides two resources:
-
A graphql-codegen
executable that can generate Haskell definitions from input .graphql
files
-
The graphql-client
Haskell library providing a runQuery
function that takes in a query type generated by graphql-codegen
Quickstart
Pre-requisites: Have Node.js installed.
-
Add graphql-client
as a dependency to your package.yaml
or Cabal file
-
stack build --only-dependencies
-
Write the .graphql
queries you wish to use.
-
Write an appropriate codegen.yml
configuration. It should look something
like:
schema: https://example.com/graphql
documents: path/to/files/*.graphql
hsSourceDir: src/
apiModule: Example.GraphQL.API
enumsModule: Example.GraphQL.Enums
scalarsModule: Example.GraphQL.Scalars
See the "Configuration" section for the full format of this file.
-
Write the module specified in scalarsModule
(e.g.
src/Example/GraphQL/Scalars.hs
). See the "Configuration" section for more
details.
-
stack exec graphql-codegen
-
The API module (e.g. src/Example/GraphQL/API.hs
) should have been
generated with the Haskell definitions needed to run your GraphQL queries.
If any of your GraphQL queries use enums, corresponding modules will also
be generated (see the "Configuration" section for more details).
The generated API creates a data type for each GraphQL query of the form
{queryName}Query
(or {queryName}Mutation
for mutations). For example, the following GraphQL query would generate the following Haskell code:
query getRecordings($query: String!, $first: Int) {
search {
recordings(query: $query, first: $first) {
nodes {
title
}
}
}
}
data GetRecordingsQuery = GetRecordingsQuery
{ _query :: Text
, _first :: Maybe Int
}
type GetRecordingsSchema = [schema|
{
search: Maybe {
recordings: Maybe {
nodes: Maybe List Maybe {
title: Maybe Text,
},
},
},
}
|]
Data.GraphQL
exports a function runQuery
which takes in one of the Query or Mutation data types and returns the response, throwing an error if the GraphQL server returns an error.
A full example of the API in action:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE QuasiQuotes #-}
import Control.Monad.IO.Class (MonadIO(..))
import Data.GraphQL
( MonadGraphQLQuery
, GraphQLSettings(..)
, defaultGraphQLSettings
, get
, runGraphQLQueryT
, runQuery
)
import qualified Data.Text as Text
import Example.GraphQL.API
app :: (MonadGraphQLQuery m, MonadIO m) => m ()
app = do
song <- Text.pack <$> liftIO getLine
result <- runQuery GetRecordingsQuery
{ _query = song
, _first = Just 5
}
-- See the `aeson-schemas` package for more information on this syntax
let songs = [get| result.search!.recordings!.nodes![]! |]
liftIO $ print $ map [get| .title! |] songs
main :: IO ()
main = do
let graphQLSettings = defaultGraphQLSettings
{ url = "https://graphbrainz.fly.dev"
-- ^ Most GraphQL APIs are at the path `/graphql`, but not this one
}
runGraphQLQueryT graphQLSettings app
Configuration
The codegen.yml
file should have the following format. All paths are
relative to the codegen.yml
file.
-
schema
: Where to get the schema of the entire GraphQL API. Can be one of
the following:
- A URL pointing to the GraphQL API
- The path to a local JSON file containing the result of a
GraphQL Introspection
query
- The path to a local
.graphql
file containing the schema in GraphQL format
-
documents
: A string or list of strings containing
glob expressions to load the
.graphql
files containing the GraphQL queries you wish to use.
-
hsSourceDir
: The directory (relative to codegen.yml
) to generate the
modules. Should be one of the directories in the hs-source-dirs
field in
your Cabal file. A module X.Y.Z
would be generated at
<hsSourceDir>/X/Y/Z.hs
. Defaults to src/
.
-
apiModule
: The module that will be generated with the Haskell definitions
corresponding to the .graphql
input files specified by documents
.
-
enumsModule
: The module where GraphQL enums will be generated. Only the
enums you actually use in your queries will be generated, with a module
generated per enum. For example, if your queries use a Color
enum and
enumsModule
is set to Example.GraphQL.Enums
, graphql-codegen
will
generate the Example.GraphQL.Enums.Color
module.
-
scalarsModule
: The module where custom GraphQL scalars should be exported.
You may define the scalars in other modules, but you must re-export them in
this module. If you're not using any custom scalars in your queries, this
module can be empty (but must still exist). All GraphQL scalars must have
FromJSON
and ToJSON
instances.
Testing
This library also provides utilities to test functions using GraphQL queries by
mocking the GraphQL endpoints. For example, you might test the app
function
from the Quickstart with the following:
{-# LANGUAGE QuasiQuotes #-}
import Data.Aeson.QQ (aesonQQ)
import Data.GraphQL.TestUtils (ResultMock(..), mocked, runMockQueryT)
import Example (app)
import Example.GraphQL.API
main :: IO ()
main = do
let mockedGetRecordings = mocked ResultMock
{ query = GetRecordingsQuery
{ _query = "My Song"
, _first = Just 5
}
, result =
[aesonQQ|
{
"search": {
"recordings": {
"nodes": []
}
}
}
|]
}
-- should not hit the server
result <- runMockQueryT app [mockedGetRecordings]
-- test `result`, which should be the result hardcoded above