Create todos - mutation

In this part of the tutorial, you will learn how to create new todos by using GraphQL Mutations.

Let's define a graphql mutation to insert into todos.

mutation ($todo: String!, $isPublic: Boolean!) {
insert_todos(objects: {title: $todo, is_public: $isPublic}) {
affected_rows
}
}

You will also need to pass in the values for the variables.

Try this mutation in GraphiQL against the application database to see what the response looks like.

Let's now integrate this graphql mutation into our elm app.

We will construct a GraphQL Mutation to insert a private todo and integrate it with the elm app.

Configure GraphQL Client

We need a client which can take a GraphQL mutation, make a network call and return a GraphQL response. elm-graphql package exposes a light weight http client which can be used to make GraphQL requests. Lets configure it to our requirements

Open src/GraphQLClient.elm and add the following code:

- module GraphQLClient exposing (makeGraphQLQuery)
+module GraphQLClient exposing (makeGraphQLMutation, makeGraphQLQuery)
import Graphql.Http
- import Graphql.Operation exposing (RootQuery)
+import Graphql.Operation exposing (RootMutation, RootQuery)
import Graphql.SelectionSet as SelectionSet exposing (SelectionSet)
+makeGraphQLMutation : String -> SelectionSet decodesTo RootMutation -> (Result (Graphql.Http.Error decodesTo) decodesTo -> msg) -> Cmd msg
+makeGraphQLMutation authToken query decodesTo =
+ query
+ |> Graphql.Http.mutationRequest graphql_url
+ {-
+ mutationRequest signature is of the form
+ String -> SelectionSet decodesTo RootMutation -> Request decodesTo
+ url -> SelectionSet TasksWUser RootMutation -> Request TasksWUser
+ -}
+ |> getAuthHeader authToken
+ |> Graphql.Http.send decodesTo

Import dependencies

Lets import the types, utility functions generated by elm-graphql into our app and construct a GraphQL query

Open src/Main.elm and add the following code:

- import GraphQLClient exposing (makeGraphQLQuery)
+import GraphQLClient exposing (makeGraphQLMutation, makeGraphQLQuery)
import Graphql.Http
- import Graphql.Operation exposing (RootQuery)
+import Graphql.Operation exposing (RootMutation, RootQuery)
import Hasura.InputObject
exposing
( Boolean_comparison_exp
, Todos_bool_exp
+ , Todos_insert_input
, Todos_order_by
, buildBoolean_comparison_exp
, buildTodos_bool_exp
+ , buildTodos_insert_input
, buildTodos_order_by
)
+import Hasura.Mutation as Mutation
+ exposing
+ ( InsertTodosRequiredArguments
+ , insert_todos
+ )
+import Hasura.Object.Todos_mutation_response as TodosMutation
import Html.Events
exposing
( onClick
, onInput
+ , onSubmit
)

Construct GraphQL Mutation

fetchPrivateTodos : String -> Cmd Msg
fetchPrivateTodos authToken =
makeGraphQLQuery authToken
fetchPrivateTodosQuery
(RemoteData.fromResult >> FetchPrivateDataSuccess)
+insertTodoObjects : String -> Bool -> Todos_insert_input
+insertTodoObjects newTodo isPublic =
+ buildTodos_insert_input
+ (\args ->
+ { args
+ | title = Present newTodo
+ , is_public = Present isPublic
+ }
+ )
+
+
+insertArgs : String -> Bool -> InsertTodosRequiredArguments
+insertArgs newTodo isPublic =
+ InsertTodosRequiredArguments [ insertTodoObjects newTodo isPublic ]
+
+
+getTodoListInsertObject : String -> Bool -> SelectionSet (Maybe MutationResponse) RootMutation
+getTodoListInsertObject newTodo isPublic =
+ insert_todos identity (insertArgs newTodo isPublic) mutationResponseSelection
+
+
+mutationResponseSelection : SelectionSet MutationResponse Hasura.Object.Todos_mutation_response
+mutationResponseSelection =
+ SelectionSet.map MutationResponse
+ TodosMutation.affected_rows
+
+
+makeMutation : SelectionSet (Maybe MutationResponse) RootMutation -> String -> Cmd Msg
+makeMutation mutation authToken =
+ makeGraphQLMutation authToken mutation (RemoteData.fromResult >> GraphQLResponse >> InsertPrivateTodoResponse)

Add Data Types

Lets add new data types required to perform this operation

type alias OnlineUser =
{ id : String
, user : User
}
+type alias MutationResponse =
+ { affected_rows : Int
+ }
+
+
+type alias MaybeMutationResponse =
+ Maybe MutationResponse
+
+
+type GraphQLResponse decodesTo
+ = GraphQLResponse (RemoteData (Graphql.Http.Error decodesTo) decodesTo)
type alias PrivateTodo =
{ todos : TodoData
, visibility : String
, newTodo : String
+ , mutateTodo : GraphQLResponse MaybeMutationResponse
}
initializePrivateTodo : PrivateTodo
initializePrivateTodo =
{ todos = RemoteData.Loading
, visibility = "All"
, newTodo = ""
+ , mutateTodo = GraphQLResponse RemoteData.NotAsked
}

Add new Msg type

Lets add Msg types required to perform this operation

type Msg
= EnteredEmail String
| EnteredPassword String
| EnteredUsername String
| MakeLoginRequest
| MakeSignupRequest
| ToggleAuthForm DisplayForm
| GotLoginResponse LoginResponseParser
| GotSignupResponse SignupResponseParser
| ClearAuthToken
| FetchPrivateDataSuccess TodoData
+ | InsertPrivateTodo
+ | UpdateNewTodo String
+ | InsertPrivateTodoResponse (GraphQLResponse MaybeMutationResponse)

Handle new Msg types in update

FetchPrivateDataSuccess response ->
updatePrivateData (\privateData -> { privateData | todos = response }) model Cmd.none
+ InsertPrivateTodoResponse response ->
+ updatePrivateData (\privateData -> { privateData | mutateTodo = response, newTodo = "" }) model (fetchPrivateTodos model.authData.authToken)
+
+ InsertPrivateTodo ->
+ case String.length model.privateData.newTodo of
+ 0 ->
+ ( model, Cmd.none )
+
+ _ ->
+ let
+ mutationObj =
+ getTodoListInsertObject model.privateData.newTodo False
+ in
+ updatePrivateData (\privateData -> { privateData | mutateTodo = GraphQLResponse RemoteData.Loading }) model (makeMutation mutationObj model.authData.authToken)
+
+ UpdateNewTodo newTodo ->
+ updatePrivateData (\privateData -> { privateData | newTodo = newTodo }) model Cmd.none

Update render functions

renderTodos : PrivateTodo -> Html Msg
renderTodos privateData =
div [ class "tasks_wrapper" ] <|
case privateData.todos of
RemoteData.NotAsked ->
[ text "" ]
RemoteData.Success todos ->
[ todoListWrapper privateData.visibility todos ]
RemoteData.Loading ->
[ span [ class "loading_text" ]
[ text "Loading todos ..." ]
]
RemoteData.Failure err ->
[ text "Error loading todos" ]
+handleMutationTodo : GraphQLResponse MaybeMutationResponse -> List (Html msg)
+handleMutationTodo (GraphQLResponse mutationTodo) =
+ case mutationTodo of
+ RemoteData.NotAsked ->
+ [ text "" ]
+
+ RemoteData.Success todos ->
+ [ text "" ]
+
+ RemoteData.Loading ->
+ [ i [ class "fa fa-spinner fa-spin" ] []
+ ]
+
+ RemoteData.Failure err ->
+ [ text "Error Mutating data:" ]
+
+
+todoMutation : GraphQLResponse MaybeMutationResponse -> Html msg
+todoMutation mutateTodo =
+ span [ class "mutation_loader" ] <|
+ handleMutationTodo mutateTodo
personalTodos : PrivateTodo -> Html Msg
personalTodos privateData =
div [ class "col-xs-12 col-md-6 sliderMenu p-30" ]
[ div [ class "todoWrapper" ]
[ div [ class "sectionHeader" ]
[ text "Personal todos"
]
- , form [ class "formInput" ]
+ , form [ class "formInput", onSubmit InsertPrivateTodo ]
- [ input [ class "input", placeholder "What needs to be done?" ]
+ [ input [ class "input", placeholder "What needs to be done?", onInput UpdateNewTodo, value privateData.newTodo ]
[]
, i [ class "inputMarker fa fa-angle-right" ] []
+ , todoMutation privateData.mutateTodo
]
, renderTodos privateData
]
]

How does this work?

Here is the summary of how this works

  • User types todo in the input box
  • OnSubmit invokes the configured function, which returns a type Msg
  • The update function is configured to listen to Msg types and hence the mutation is executed

Woot! You have successfully written your first GraphQL mutation with Elm. Easy isn't it?