2016-03-09 01:07:57 +01:00
|
|
|
---
|
|
|
|
title: GraphQL Haskell Tutorial
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
== Getting started ==
|
|
|
|
|
|
|
|
Welcome to graphql-haskell!
|
|
|
|
|
2020-07-02 07:33:03 +02:00
|
|
|
We have written a small tutorial to help you (and ourselves) understand the
|
|
|
|
graphql package.
|
2016-03-09 01:07:57 +01:00
|
|
|
|
2020-07-02 07:33:03 +02:00
|
|
|
Since this file is a literate haskell file, we start by importing some
|
|
|
|
dependencies.
|
2016-03-09 01:07:57 +01:00
|
|
|
|
|
|
|
> {-# LANGUAGE OverloadedStrings #-}
|
|
|
|
> module Main where
|
|
|
|
>
|
2019-07-20 06:57:13 +02:00
|
|
|
> import Control.Monad.IO.Class (liftIO)
|
|
|
|
> import Data.Aeson (encode)
|
2016-03-09 01:07:57 +01:00
|
|
|
> import Data.ByteString.Lazy.Char8 (putStrLn)
|
2020-05-23 06:46:21 +02:00
|
|
|
> import qualified Data.HashMap.Strict as HashMap
|
2019-07-20 06:57:13 +02:00
|
|
|
> import Data.Text (Text)
|
2020-05-23 06:46:21 +02:00
|
|
|
> import qualified Data.Text as Text
|
2019-07-20 06:57:13 +02:00
|
|
|
> import Data.Time (getCurrentTime)
|
2016-03-09 01:07:57 +01:00
|
|
|
>
|
2019-07-20 06:57:13 +02:00
|
|
|
> import Language.GraphQL
|
2020-05-25 07:41:21 +02:00
|
|
|
> import Language.GraphQL.Type
|
|
|
|
> import qualified Language.GraphQL.Type.Out as Out
|
2016-03-09 01:07:57 +01:00
|
|
|
>
|
2019-07-20 06:57:13 +02:00
|
|
|
> import Prelude hiding (putStrLn)
|
2016-03-09 01:07:57 +01:00
|
|
|
|
2020-07-02 07:33:03 +02:00
|
|
|
|
2016-03-09 01:07:57 +01:00
|
|
|
=== First example ===
|
|
|
|
|
2020-07-02 07:33:03 +02:00
|
|
|
Now, as our first example, we are going to look at the example from
|
|
|
|
[graphql.js](https://github.com/graphql/graphql-js).
|
2016-03-09 01:07:57 +01:00
|
|
|
|
|
|
|
First we build a GraphQL schema.
|
|
|
|
|
2020-05-14 09:17:14 +02:00
|
|
|
> schema1 :: Schema IO
|
|
|
|
> schema1 = Schema queryType Nothing
|
|
|
|
>
|
|
|
|
> queryType :: ObjectType IO
|
2020-05-26 11:13:55 +02:00
|
|
|
> queryType = ObjectType "Query" Nothing []
|
2020-06-29 13:14:23 +02:00
|
|
|
> $ HashMap.singleton "hello" helloField
|
2020-06-03 07:20:38 +02:00
|
|
|
>
|
|
|
|
> helloField :: Field IO
|
2020-06-29 13:14:23 +02:00
|
|
|
> helloField = Field Nothing (Out.NamedScalarType string) mempty hello
|
2016-03-09 01:07:57 +01:00
|
|
|
>
|
2020-06-29 13:14:23 +02:00
|
|
|
> hello :: ResolverT IO Value
|
2020-05-27 23:18:35 +02:00
|
|
|
> hello = pure $ String "it's me"
|
2016-03-09 01:07:57 +01:00
|
|
|
|
2020-07-02 07:33:03 +02:00
|
|
|
This defines a simple schema with one type and one field, that resolves to a
|
|
|
|
fixed value.
|
2016-03-09 01:07:57 +01:00
|
|
|
|
|
|
|
Next we define our query.
|
|
|
|
|
|
|
|
> query1 :: Text
|
|
|
|
> query1 = "{ hello }"
|
|
|
|
|
|
|
|
To run the query, we call the `graphql` with the schema and the query.
|
|
|
|
|
|
|
|
> main1 :: IO ()
|
2020-06-13 07:20:19 +02:00
|
|
|
> main1 = graphql schema1 query1 >>= putStrLn . encode
|
2016-03-09 01:07:57 +01:00
|
|
|
|
2020-07-02 07:33:03 +02:00
|
|
|
This runs the query by fetching the one field defined, returning
|
2016-03-09 01:07:57 +01:00
|
|
|
|
|
|
|
```{"data" : {"hello":"it's me"}}```
|
|
|
|
|
|
|
|
|
|
|
|
=== Monadic actions ===
|
|
|
|
|
|
|
|
For this example, we're going to be using time.
|
|
|
|
|
2020-05-14 09:17:14 +02:00
|
|
|
> schema2 :: Schema IO
|
|
|
|
> schema2 = Schema queryType2 Nothing
|
|
|
|
>
|
|
|
|
> queryType2 :: ObjectType IO
|
2020-05-26 11:13:55 +02:00
|
|
|
> queryType2 = ObjectType "Query" Nothing []
|
2020-06-29 13:14:23 +02:00
|
|
|
> $ HashMap.singleton "time" timeField
|
2020-06-03 07:20:38 +02:00
|
|
|
>
|
|
|
|
> timeField :: Field IO
|
2020-06-29 13:14:23 +02:00
|
|
|
> timeField = Field Nothing (Out.NamedScalarType string) mempty time
|
2016-03-09 01:07:57 +01:00
|
|
|
>
|
2020-06-29 13:14:23 +02:00
|
|
|
> time :: ResolverT IO Value
|
2020-05-25 07:41:21 +02:00
|
|
|
> time = do
|
2019-12-31 08:29:03 +01:00
|
|
|
> t <- liftIO getCurrentTime
|
2020-05-27 23:18:35 +02:00
|
|
|
> pure $ String $ Text.pack $ show t
|
2016-03-09 01:07:57 +01:00
|
|
|
|
2020-07-02 07:33:03 +02:00
|
|
|
This defines a simple schema with one type and one field, which resolves to the
|
|
|
|
current time.
|
2016-03-09 01:07:57 +01:00
|
|
|
|
|
|
|
Next we define our query.
|
|
|
|
|
|
|
|
> query2 :: Text
|
|
|
|
> query2 = "{ time }"
|
|
|
|
>
|
|
|
|
> main2 :: IO ()
|
2020-06-13 07:20:19 +02:00
|
|
|
> main2 = graphql schema2 query2 >>= putStrLn . encode
|
2016-03-09 01:07:57 +01:00
|
|
|
|
|
|
|
This runs the query, returning the current time
|
|
|
|
|
|
|
|
```{"data": {"time":"2016-03-08 23:28:14.546899 UTC"}}```
|
|
|
|
|
|
|
|
|
|
|
|
=== Errors ===
|
|
|
|
|
2020-07-02 07:33:03 +02:00
|
|
|
Errors are handled according to the spec, with fields that cause erros being
|
|
|
|
resolved to `null`, and an error being added to the error list.
|
2016-03-09 01:07:57 +01:00
|
|
|
|
|
|
|
An example of this is the following query:
|
|
|
|
|
|
|
|
> queryShouldFail :: Text
|
|
|
|
> queryShouldFail = "{ boyhowdy }"
|
|
|
|
|
2020-07-02 07:33:03 +02:00
|
|
|
Since there is no `boyhowdy` field in our schema, it will not resolve, and the
|
|
|
|
query will fail, as we can see in the following example.
|
2016-03-09 01:07:57 +01:00
|
|
|
|
|
|
|
> mainShouldFail :: IO ()
|
|
|
|
> mainShouldFail = do
|
2019-07-20 06:57:13 +02:00
|
|
|
> failure <- graphql schema1 queryShouldFail
|
|
|
|
> putStrLn $ encode failure
|
2016-03-09 01:07:57 +01:00
|
|
|
|
|
|
|
This outputs:
|
|
|
|
|
|
|
|
```
|
|
|
|
{"data": {"boyhowdy": null}, "errors":[{"message": "the field boyhowdy did not resolve."}]}
|
|
|
|
```
|
|
|
|
|
2020-07-02 07:33:03 +02:00
|
|
|
|
2016-03-09 01:07:57 +01:00
|
|
|
=== Combining resolvers ===
|
|
|
|
|
|
|
|
Now that we have two resolvers, we can define a schema which uses them both.
|
|
|
|
|
2020-05-14 09:17:14 +02:00
|
|
|
> schema3 :: Schema IO
|
|
|
|
> schema3 = Schema queryType3 Nothing
|
|
|
|
>
|
|
|
|
> queryType3 :: ObjectType IO
|
2020-05-26 11:13:55 +02:00
|
|
|
> queryType3 = ObjectType "Query" Nothing [] $ HashMap.fromList
|
2020-06-29 13:14:23 +02:00
|
|
|
> [ ("hello", helloField)
|
|
|
|
> , ("time", timeField)
|
2020-05-23 06:46:21 +02:00
|
|
|
> ]
|
2016-03-09 01:07:57 +01:00
|
|
|
>
|
|
|
|
> query3 :: Text
|
|
|
|
> query3 = "query timeAndHello { time hello }"
|
|
|
|
>
|
|
|
|
> main3 :: IO ()
|
2020-06-13 07:20:19 +02:00
|
|
|
> main3 = graphql schema3 query3 >>= putStrLn . encode
|
2016-03-09 01:07:57 +01:00
|
|
|
|
|
|
|
This queries for both time and hello, returning
|
|
|
|
|
|
|
|
```{ "data": {"hello":"it's me","time":"2016-03-08 23:29:11.62108 UTC"}}```
|
|
|
|
|
2020-07-02 07:33:03 +02:00
|
|
|
Notice that we can name our queries, as we did with `timeAndHello`. Since we
|
|
|
|
have only been using single queries, we can use the shorthand `{ time hello }`,
|
|
|
|
as we have been doing in the previous examples.
|
2016-03-09 01:07:57 +01:00
|
|
|
|
|
|
|
In GraphQL there can only be one operation per query.
|
|
|
|
|
|
|
|
|
|
|
|
== Further examples ==
|
|
|
|
|
2020-07-02 07:33:03 +02:00
|
|
|
More examples on queries and a more complex schema can be found in the test
|
|
|
|
directory, in the [Test.StarWars](../../tests/Test/StarWars) module. This
|
|
|
|
includes a more complex schema, and more complex queries.
|
2019-07-20 06:57:13 +02:00
|
|
|
|
|
|
|
> main :: IO ()
|
|
|
|
> main = main1 >> main2 >> mainShouldFail >> main3
|